python 装饰器的定义: 在代码运行期间在不改变原函数定义的基础上, 动态给该函数增加功能的方式称之为装饰器(Decorator)
装饰器的优点和用途:
1. 抽离出大量函数中与函数功能本身无关的的雷同代码并继续重用.
2. 使用装饰器可以将函数 "修饰" 为完全不同的行为, 可以有效的将业务逻辑正交分解, 如用于将权限与身份验证从业务中独立出来.
3. 如果一个函数需要一个功能, 且这个功能可以被使用在很多函数上, 或是函数并不是自己实现, 那可以写个装饰器来实现这些功能.
概况来说, 装饰器的作用就是为已经存在的对象添加一些额外的功能.
在学习如何运用装饰器前我们先来学习以下几个知识点:
1. 变量的作用域:
在 python 中, 函数会创建一个自己的作用域或称之为命名空间, 结合以下示例来展示函数命名空间的作用域.
示例代码 1:
- #coding=utf-8
- outerVar = "this is a global variable"
- def test() :
- innerVar = "this is a Local variable"
- print ("local variables :")
- print (locals()) #locals 函数返回的是函数 test()内部本地作用域中的可用的变量名称的字典(变量名: 值)
- test()
- print ("global variables :")
- print (globals()) #globals 函数返回的是 python 程序中的可用的变量名称的字典(变量名: 值)
- # 执行结果:
- local variables :
- {'innerVar': 'this is a Local variable'}
- global variables :
- {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000015848FE87F0>,
- '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'test.py', '__cached__': None,'outerVar': 'this is a global variable',
- 'test': <function test at 0x0000015848E11E18>}
2. 变量解析规则:
在 python 的作用域规则里面, 创建变量时一定会在当前作用域里创建同样的变量, 但访问或修改变量时, 会在当前作用域中查找该变量, 如果没找到匹配的变量, 就会依次向上在闭合作用域中进行查找, 所以也可以在函数中直接访问全局变量.
示例代码 2:
- #coding=utf-8
- outerVar = "this is a global variable"
- def test() :
- innerVar = "this is a Local variable"
- print (outerVar) #获取全局变量 outerVar 值
- print (n) #获取全局变量 n 的值
- n = 33
- test()
- # 执行结果:
- this is a global variable
- 33
3. 变量的生存空间:
变量不仅仅是存在于一个个的命名空间中, 它们还都有自己的生存周期, 全局变量的生存周期是在整个程序执行期间有效, 而局部变量的生存周期只在当前作用域中有效, 一旦这个作用域不存在了, 比如函数执行退出了, 变量的生存周期就结束了.
示例代码 3:
- #coding=utf-8
- outerVar = "this is a global variable"
- def test() :
- innerVar = "this is a Local variable"
- test()
- print (innerVar) #test 函数执行结束后, innerVar 变量已释放, 再访问函数内部变量就会报 NameError
执行结果:
- Traceback (most recent call last):
- File "test.py", line 56, in <module>
- print (innerVar)
- NameError: name 'innerVar' is not defined
4. 嵌套函数:
定义: 嵌套函数就是在函数里面定义函数, 而且现有的作用域和变量生存周期依旧不变.
说明: 在 python 里, 函数就是对象, 它也只是一些普通的值而已. 也就是说你可以把函数像参数一样传递给其他的函数或者说从函数了里面返回函数.
示例代码 4:
- #coding=utf-8
- def outer() :
- name = "python"
- def inner() :
- #获取 name 变量值时, python 解析器默认会先在函数内部查找, 查找失败后, 继续往上一层函数作用域查找.
- print(name)
- #python 解释器会优先在 outer 的作用域里面查找变量名为 inner 的变量. 把作为函数标识符的变量 inner 作为返回值返回.
每次函数 outer 被调用的时候, 函数 inner 都会被重新定义, 如果它不被当做变量返回的话, 每次执行过后它将不复存在.
- return inner()
- print (outer())
结果:
- python
- None #inner 函数默认返回值为 None
嵌套函数返回函数不加 () 表示返回函数对象, 如下示例 5 所示:
示例代码 5:
- #coding=utf-8
- def outer() :
- name = "python"
- def inner() :
- print( name)
- return inner #表示返回函数对象
- print (outer())
执行结果:
<function outer.<locals>.inner at 0x0000027446B6A9D8>
5. 函数作为变量:
python 中函数可以作为参数来传递
示例代码 6:
- #coding=utf-8
- def add(x, y):
- return x + y
- def sub(x, y):
- return x - y
- def apply(func, x, y):
- return func(x, y)
- print (apply(add, 2, 1)) #add 函数作为 apply 函数的参数传递
- print (apply(sub, 2, 1))
执行结果:
3
1
6. 闭包:
定义: 如果一个函数定义在另一个函数的作用域内, 并且引用了外层函数的变量, 则该函数称为闭包.
一个函数返回的函数对象, 这个函数对象执行需要依赖外部函数的变量值, 这个时候函数返回的实际内容如下:
1. 函数对象
2. 函数对象执行需要使用的外部变量和变量值.
简而言之: 闭包就是返回一个函数和一个函数执行所需要的外部变量.
示例代码 7:
- #coding=utf-8
- def outer():
- name = "python"
- def inner() :
- print (name) #函数使用了外部的 name 变量
- return inner #返回函数对象
- res = outer() #调用 outer()方法, 返回 inner 函数对象
- res() #inner 函数的对象 +()= 调用函数 inner()
- print (res.func_closure)# 查看函数包含哪些外部变量
- #print (res()) #注意使用 print 打印返回结果为 name 值 + inner 函数默认返回值 None
执行结果:
- python
- (<cell at 0x0000000002706CD8: str object at 0x0000000002708738>,) #外部变量是一个 str 类型
上例中的 inner()函数就是一个闭包, 它本身也是一个函数, 而且还可以访问本身之外的变量.
每次函数 outer 被调用时, inner 函数都会被重新定义, 示例代码 7 每次返回的函数 inner 结果都一样, 因为 name 没变. 如下例所示, 我们将函数稍微改动
一下, 结果就不一样了.
示例代码 8:
- #coding=utf-8
- def outer(name) :
- def inner() :
- print (name)
- return inner
- res1 = outer("python")
- res2 = outer("java")
- res1()
- res2()
执行结果:
python
java
学习了以上 6 个小知识点, 下面开始学习装饰器.
装饰器的定义:
装饰器其实就是一个闭包, 把一个函数当做参数后返回一个替代版函数.
装饰器分类:
装饰器分为无参数 decorator 和有参数 decorator
无参数 decorator: 生成一个新的装饰器函数
有参数 decorator: 装饰函数先处理参数, 再生成一个新的装饰器函数, 然后对函数进行装饰.
装饰器的具体定义:
1, 把要装饰的方法作为输入参数;
2, 在函数体内可以进行任意的操作;
3, 只要确保最后返回一个可执行的函数即可(可以是原来的输入参数函数, 也可以是一个新函数)
装饰器学习七步法:
第一步: 最简单的函数, 准备附加额外功能
- # -*- coding:utf-8 -*-
- # 最简单的函数, 表示调用了两次函数
- def myfunc():
- print "myfunc() called."
- myfunc()
- myfunc()
执行结果:
- myfunc() called.
- myfunc() called.
第二步: 使用装饰函数在函数执行前和执行后分别附加额外功能
- # -*- coding:utf-8 -*-
- '''示例 2: 替换函数(装饰) , 装饰函数的参数是被装饰的函数对象, 返回原函数对象. 装饰的实质语句: myfunc = deco(myfunc)'''
- def deco(func):
- print ("before myfunc() called.") #在 func()函数执行前附加功能, 打印一句话
- func() #被执行函数
- print ("after myfunc() called.") #在 func()函数执行后附加功能, 打印一句话
- return func
- def myfunc():
- print ("myfunc()called.")
- new_myfunc = deco(myfunc) #表示调用参数为 myfunc 函数对象的 deco()函数, 结果返回 func 函数对象并赋值给 myfunc
- new_myfunc() #表示调用 myfunc 函数
- new_myfunc() #表示调用 myfunc 函数
结果:
- before myfunc() called.
- myfunc()called.
- after myfunc() called.
- myfunc()called.
- myfunc()called.
解析:
1.myfunc = deco(myfunc)执行结果:
- before myfunc() called.
- myfunc()called.
- after myfunc() called.
2. 第一次调用 myfunc()执行结果:
myfunc()called.
3. 第二次调用 myfunc()执行结果:
myfunc()called.
从第 2 和第 3 次调用 myfunc 函数来看, 并没有实现每次调用都返回第 1 次调用的效果, 那么我们要实现每次调用都带有附加功能的效果, 我们后面会 = 逐步实现.
第三步: 使用 @符号来装饰函数
- # -*- coding:utf-8 -*-
- '''示例 3: 使用 @符号装饰函数, 相当于"myfunc = deco(myfunc)", 但发现新函数只在第一次被调用, 且原函数多调用了一次. 等价于第二步程序'''
- def deco(func):
- print ("before myfunc() called.")
- func()
- print ("after myfunc() called.")
- return func
- @deco
- def myfunc():
- print ("myfunc()called.")
- myfunc()
- myfunc()
- # 执行结果:
- before myfunc() called.
- myfunc()called.
- after myfunc() called.
- myfunc()called.
- myfunc()called.
第四步: 使用内嵌包装函数来确保每次新函数都被调用
- # -*- coding:utf-8 -*-
- '''示例 4: 使用内嵌包装函数来确保每次新函数都被调用, 内嵌包装函数的形参和返回值与原函数相同, 装饰函数返回内嵌包装函数对象'''
- def deco(func):
- def _deco():
- print ("before myfunc() called.")
- func()
- print ("after myfunc() called.")
- # 不需要返回 func, 实际上应返回原函数的返回值
- return _deco
- @deco
- def myfunc():
- print ("myfunc() called.")
- return 'ok'
- myfunc()
- myfunc()
执行结果:
- before myfunc() called.
- myfunc() called.
- after myfunc() called.
- before myfunc() called.
- myfunc() called.
- after myfunc() called.
上面是实现了 1 个函数使用装饰器的例子, 下面演示 2 个函数分别使用装饰器的实例:
- # -*- coding:utf-8 -*-
- '''增加打印函数执行时间功能, 分别统计两个函数的执行效率'''
- import time
- def deco(func):
- def _deco():
- start_time=time.time()
- func()
- end_time=time.time()
- print("执行总耗时:",end_time-start_time)
- return _deco
- @deco
- def myfunc():
- print ("myfunc() called.")
- time.sleep(1)
- return 'true'
- @deco
- def youyfunc():
- print ("youyfunc() called.")
- time.sleep(2)
- return 'true'
- myfunc()
- print ('*'*20)
- youyfunc()
- # 结果:
- #myfunc() called.
- # 执行总耗时: 1.0080790519714355
- #********************
- # youyfunc() called.
- # 执行总耗时: 2.0119848251342773
- # 执行过程解析:
执行 myfunc()等价于执行 deco(myfunc)()
- # 首先 myfunc 函数作为参数传递给了 deco()函数, 形参 func 被替换为实参 myfunc,deco()函数开始顺序执行_deco()函数,
- # 先调用了 myfunc()函数, 开始执行 myfunc 函数, 打印执行总耗时, 最后返回_deco()函数对象.
说明: 使用装饰器的函数之间变量不会互相影响, 等于每次调用都会重新生成一个_deco 函数.
第五步: 实现对带参数的函数进行装饰
- # -*- coding:utf-8 -*-
- '''内嵌包装函数的形参和返回值与原函数相同, 装饰函数返回内嵌包装函数对象'''
- def deco(func):
- def _deco(a, b):
- print ("before myfunc() called.")
- ret = func(a, b)
- print ("after myfunc() called. result: %s" % ret)
- return ret
- return _deco
- @deco
- def myfunc(a, b):
- print ("myfunc(%s,%s) called."%(a, b))
- return a + b
- myfunc(1, 2) #使用 print (myfunc(1, 2)) 查看 return ret 的结果
- myfunc(3, 4)
执行结果:
- before myfunc() called.
- myfunc(1,2) called.
- after myfunc() called. result: 3
- before myfunc() called.
- myfunc(3,4) called.
- after myfunc() called. result: 7
第六步: 对参数数量不确定的函数进行装饰
- # -*- coding:utf-8 -*-
- '''参数用(*args, **kwargs), 自动适应变参和命名参数'''
- def deco(func):
- def _deco(*args, **kwargs):
- print ("before %s called." % func.__name__)
- ret = func(*args, **kwargs)
- print ("after %s called. result: %s" % (func.__name__, ret))
- return ret
- return _deco
- @deco
- def myfunc1(a, b):
- print ("myfunc(%s,%s) called." % (a, b))
- return a+b
- @deco
- def myfunc2(a, b, c):
- print ("myfunc2(%s,%s,%s) called." % (a, b, c))
- return a+b+c
- myfunc1(1, 2)
- myfunc1(3, 4)
- myfunc2(1, 2, 3)
- myfunc2(3, 4, 5)
- # 结果
- before myfunc1 called.
- myfunc(1,2) called.
- after myfunc1 called. result: 3
- before myfunc1 called.
- myfunc(3,4) called.
- after myfunc1 called. result: 7
- before myfunc2 called.
- myfunc2(1,2,3) called.
- after myfunc2 called. result: 6
- before myfunc2 called.
- myfunc2(3,4,5) called.
- after myfunc2 called. result: 12
第七步: 装饰器带可变参数
- # -*- coding:utf-8 -*-
- '''在装饰器第四步 4 的基础上, 让装饰器带参数, 和上一示例相比在外层多了一层包装. 装饰函数名实际上应更有意义些'''
- def deco(arg):
- def _deco(func):
- def __deco():
- print ("before %s called [%s]." % (func.__name__, arg))
- func()
- print ("after %s called [%s]." % (func.__name__, arg))
- return __deco
- return _deco
- @deco("mymodule1") #装饰器参数是一个字符串, 本身没有含义
- def myfunc():
- print ("myfunc() called.")
- @deco("mymodule2")
- def myfunc2():
- print ("myfunc2() called.")
- myfunc() #调用过程等价于: deco("mymodule1")()()-->_deco()()-->__deco()
- myfunc2()
- # 解析三组闭包:
1. deco("mymodule1")()()+arg-->返回_deco 函数对象
2. _deco()()+arg+func -->返回__deco 函数对象
3. __deco()+arg+func -- 返回函数最终执行结果
执行结果:
- before myfunc called [mymodule1].
- myfunc() called.
- after myfunc called [mymodule1].
- before myfunc2 called [mymodule2].
- myfunc2() called.
- after myfunc2 called [mymodule2].
装饰器顺序:
当同时对一个函数使用多个不同的装饰器进行装饰时, 这个时候装饰器的顺序就很重要了.
代码示例:
- @A
- @B
- @C
- def f():
- pass
等价于:
f = A(B(C(f)))
来源: https://www.cnblogs.com/ssj0723/p/10408271.html