装饰器:用于在源码中“标记”函数,在代码运行期间动态增强函数的行为的方式。
在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
函数是可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
我们先看看装饰器的第一个特性
1、动态增强被装饰函数的功能
import time
def now():
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
now()
输出:
2020-07-07 14:04:37
现在我们要增强这个函数,比如说在打印时间时自动打印一句话,“此刻的日期是:”,但是我们又不想修改原来的now()的函数代码,此时就可以利用装饰器。
```python
import time
def dec_fun(func):#装饰函数要定义在被装饰函数之前
def wrapper(*args, **kwargs):
print("此刻的日期是:")
return func(*args, **kwargs)
return wrapper
@dec_fun#表示用dec_fun函数装饰now()
def now():
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
now()
输出:
此刻的日期是:
2020-07-07 14:12:33
2、装饰器还可以将被装饰函数替换成其他函数。
此时只要我们在装饰器函数中不返回传入装饰器的函数,而是只返回装饰器定义的函数就行了。
import time
def dec_fun(func):#装饰函数要定义在被装饰函数之前
def wrapper(*args, **kwargs):
print("不要打印日期了")
return wrapper
@dec_fun#表示用dec_fun函数装饰now()
def now():
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
now()
输出:
不要打印日期了
3、当装饰器也需要传入自己的参数
前面说的装饰器都是没有自己的参数的,它传入的是被装饰的函数,当装饰器也需要传入自己的函数时,怎么办呢?只要在之前的装饰器外再套一个带参数的函数,这个函数返回之前的装饰器。
import time
def log(name):
def dec_fun(func):
def wrapper(*args,**kwargs):
print(name+"正在打印日期:")
return func(*args, **kwargs)
return wrapper
return dec_fun
@log("python")
def now():
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
now()
输出:
python正在打印日期:
2020-07-07 14:24:01
4、装饰器在加载模块是立即执行
装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即 Python 加载模块时)
registry = []
def register(func):
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
def main():
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main()
输出:
running register(<function f1 at 0x000001B8F02072F0>)
running register(<function f2 at 0x000001B8F0207378>)
running f1()
running f2()
running f3()
running main()
registry -> [<function f1 at 0x000001B8F02072F0>, <function f2 at 0x000001B8F0207378>]
registry = []
def register(func):
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
def main():
print('running main()')
print('registry ->', registry)
# f1()
# f2()
# f3()
if __name__=='__main__':
main()
输出:
running register(<function f1 at 0x00000257DFBC72F0>)
running register(<function f2 at 0x00000257DFBC7378>)
running main()
registry -> [<function f1 at 0x00000257DFBC72F0>, <function f2 at 0x00000257DFBC7
从上面可以看出,被装饰函数定义之后,装饰器立即执行。
5、不带参数的类装饰器
以上都是基于函数实现的装饰器,基于类装饰器的实现,类必须实现__init__和__call__两个内置函数。
init:接收被装饰函数
call:实现装饰逻辑
class logger(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("[INFO]: the function {func}() id running..."
.format(func=self.func.__name__))
return self.func(*args, **kwargs)
@logger
def say(something):
print("say {}!".format(something))
if __name__ == "__main__":
say("hello")
#############################################
[INFO]: the function say() id running...
say hello!
6、带参数的类装饰器
上面不带参数的例子,你发现没有,只能打印INFO级别的日志,正常情况下,我们还需要打印DEBUG WARNING等级别的日志。这就需要给类装饰器传入参数,给这个函数指定级别了。带参数和不带参数的类装饰器有很大的不同。
__ init __ :不再接收被装饰函数,而是接收传入参数。
__ call __ :接收被装饰函数,实现装饰逻辑。
class logger(object):
def __init__(self, level="INFO"):
self.level = level
def __call__(self, func):
def wrapper(*args,**kwargs):
print("[{level}]: the function {func}() id running..."
.format(level=self.level,func=func.__name__))
func(*args, **kwargs)
return wrapper
@logger(level="WARNING")
def say(something):
print("say {}!".format(something))
if __name__ == "__main__":
say("hello")
################################################
[WARNING]: the function say() id running...
say hello!
7、闭包
闭包是指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。
闭包一种函数,它会保留定义函数时存在的自由变量(未在本地作用域绑定的变量)的绑定,这样调用函数时,虽然定义域不可用了,但是仍能使用那些绑定。
nonlocal声明可以将变量标记为自由变量,即在函数中为变量赋予新值了,也会变成自由变量