Python 装饰器
装饰器(decorator)是Python中的高级语法。装饰的意思就是动态扩展被装饰对象的功能。装饰器可以用于装饰函数、方法和类。
学习装饰器之前,先了解函数的嵌套和闭包。
函数嵌套
所谓函数的嵌套就是在函数中再定义一个函数。
# 定义一个外层函数
def foo():
# 定义了一个内层函数
def bar():
print("hello world")
return bar
func = foo() # func --> bar 函数名即变量名
func() # 这里执行func其实就相当于执行了在foo函数内部定义的bar函数,打印 hello world
闭包
看一个例子
def foo():
name = "lcg" # 定义了一个foo函数内部的局部变量
def bar():
print(name) # 在bar函数内部引用了其外部函数foo的局部变量name
return bar
现在,我们来调用一下 foo 函数:
func = foo() func()
上面的代码,输出结果是:
lcg
bar函数不需要传参数,就能获取到外面的变量。这就是典型的一个闭包现象。
闭包的定义
bar在foo函数的代码块中定义。我们称bar是foo的内部函数。
在bar的局部作用域中可以直接访问foo局部作用域中定义的name变量。
简单的说,这种内部函数可以使用外部函数变量的行为,就叫闭包。
注意,即使你在调用 func 之前,定义一个全局变量 name,它还是会使用原来foo 函数内部的 name
func = foo() name = "lucky boy" func()
输出:
lcg
此时调用 func函数时它会使用自己定义阶段引用的外部函数的局部变量 name ,而不使用调用阶段的全局变量。
闭包的实质
闭包是由函数和与它相关的引用环境组合而成的实体。
Python中可以通过以下命令查看闭包相关信息:
print(func.__closure__)
输出:
(<cell at 0x0000021EC09A55B8: str object at 0x0000021EC0A38068>,)
__closure__ 属性定义的是一个包含 cell 对象的元组,其中元组中的每一个 cell 对象用来保存外部函数作用域中内部函数调用的变量的值。
看一下对比:
# 引用内部函数的变量 def foo(): name = "lcg" def bar(): x = 1 print(name, x) return bar func = foo() print(func.__closure__) # (<cell at 0x0000023C860055B8: str object at 0x0000023C86098068>,) # 引用外部函数的变量 def foo(): name = "lcg" x = 1 def bar(): print(name, x) return bar func = foo() print(func.__closure__) # (<cell at 0x0000023EC39155B8: str object at 0x0000023EC39A8068>, <cell at 0x0000023EC39155E8: int object at 0x00000000548E60E0>)
可以通过以下命令查看闭包具体包含的变量值:
print(func.__closure__[0].cell_contents)
输出:
lcg
像下面的 func函数就不能称为闭包,因为它并没有使用包含它的外部函数的局部变量。
name = "lcg" # 定义了一个全局变量
def foo():
def bar():
print(name) # 在bar函数内部引用了全局变量name
return bar
func = foo()
此时:
print(func.__closure__)
输出:
None
闭包的第二种形态:
def foo(name): # 给一个函数传参也相当于给函数定义了一个局部变量
# 定义了一个内部函数
def bar():
print(name) # 内部函数同样可以获取到传到外部函数的变量(参数)
return bar
func = foo("lcg") # 把“lcg”当成参数传入foo函数 --> 其内部定义的bar函数也能拿到这个“lcg”
func()
# lcg
装饰器
为什么要有装饰器?
在学习装饰器之前,一定要了解一个开放封闭原则。软件开发都应该遵循开放封闭原则。
开放封闭原则:
- 对扩展是开放的
- 对修改是封闭的
为什么说要对扩展是开放的呢?
因为软件开发过程不可能一次性把所有的功能都考虑完全,肯定会有不同的新功能需要不停添加。也就是说需要我们不断地去扩展已经存在代码的功能,这是非常正常的情况。
那为什么说对修改是封闭的呢?
比如说已经上线运行的源代码,比如某个函数内部的代码是不建议直接修改的。因为函数的使用分为两个阶段:函数的定义阶段和函数的调用阶段。因为你不确定这个函数究竟在什么地方被调用了,你如果粗暴的修改了函数内部的源代码,对整个程序造成的影响是不可控的。
总结一下就是:不修改源代码,不修改调用方式,同时还要加上新功能。
在Python中就可以使用装饰器来实现上面的需求。
什么是装饰器?
简单理解就是装饰其他对象(可调用对象)的工具。
装饰器应用了函数闭包的原理
装饰器例子:
# 还是定义一个外层函数
def foo(func): # 我接收的参数是一个函数名
# 定义了一个内部函数
def bar():
print("这是新功能。。。") # 新功能
func() # 函数名加()就相当于执行
return bar
# 定义一个被装饰的函数
def f1():
print("hello world.")
# 用foo函数装饰f1函数
f1 = foo(f1)
# 不改变f1的调用方式
f1() # --> 此时函数已经扩展了新功能
输出:
这是新功能。。。 hello world.
语法糖
使用f1 = foo(f1)语法装饰的话稍显啰嗦,Python就提供了@语法,让装饰过程更简便
def foo(name):
def bar():
print("这是新功能。。。")
name()
return bar
@foo
def f1():
print("hello world.")
f1() # --> 此时函数已经扩展了新功能
输出:
这是新功能。。。 hello world.
现在,我们已经明白了装饰器的原理。接下来,我们还有很多事情需要搞清楚。比如:装饰带参数的函数、多个装饰器同时装饰一个函数、带参数的装饰器和类装饰器。
装饰带参数的函数
def foo(func): # 接收的参数是一个函数名
def bar(x, y): # 这里需要定义和被装饰函数相同的参数
print("这里是新功能...") # 新功能
func(x, y) # 被装饰函数名和参数都有了,就能执行被装饰函数了
return bar
# 定义一个需要两个参数的函数
@foo
def f1(x, y):
print("{}+{}={}".format(x, y, x + y))
# 调用被装饰函数
f1(100, 200)
输出:
这里是新功能... 100+200=300
多个装饰器
def wrapper1(func):
def inner():
print('w1,before')
func()
print('w1,after')
return inner
def wrapper2(func):
def inner():
print('w2,before')
func()
print('w2,after')
return inner
@wrapper2
@wrapper1
def foo():
print('foo')
foo()
输出:
w2,before w1,before foo w1,after w2,after
带参数装饰器
被装饰的函数可以带参数,装饰器同样也可以带参数。
回头看我们上面写得那些装饰器,它们默认把被装饰的函数当成唯一的参数。但是呢,有时候我们需要为我们的装饰器传递参数,这种情况下应该怎么办呢?
接下来,我们就一步步实现带参数的装饰器
首先我们来回顾下上面的代码:
def f1(func): # f1是我们定义的装饰器函数,func是被装饰的函数
def f2(*arg, **kwargs): # *args和**kwargs是被装饰函数的参数
func(*arg, **kwargs)
return f2
从上面的代码,我们发现了什么?
我的装饰器如果有参数的话,没地方写了…怎么办呢?
还是要使用闭包函数!
我们需要知道,函数除了可以嵌套两层,还能嵌套更多层:
# 三层嵌套的函数
def f1():
def f2():
name = "lcg"
def f3():
print(name)
return f3
return f2
f = f1() # f --> f2
ff = f() # ff --> f3
ff() # ff() --> f3() --> print(name) --> lcg
注意:在内部函数f3中能够访问到它外层函数f2中定义的变量,当然也可以访问到它最外层函数f1中定义的变量。
def f1():
name = "lcg"
def f2():
def f3():
print(name)
return f3
return f2
f = f1()
ff = f()
ff() # --> lcg
好了,现在我们就可以实现我们的带参数的装饰器函数了:
# 带参数的装饰器需要定义一个三层的嵌套函数
def d(name): # d是新添加的最外层函数,为我们原来的装饰器传递参数,name就是我们要传递的函数
def f1(func): # f1是我们原来的装饰器函数,func是被装饰的函数
def f2(*arg, **kwargs): # f2是内部函数,*args和**kwargs是被装饰函数的参数
print(name) # 使用装饰器函数的参数
func(*arg, **kwargs) # 调用被装饰的函数
return f2
return f1
上面就是一个带参装饰器的代码示例,现在我们来写一个完整的应用:
def d(a=None): # 定义一个外层函数,给装饰器传参数a默认是None
def foo(func): # foo是我们原来的装饰器函数,func是被装饰的函数
def bar(*args, **kwargs): # args和kwargs是被装饰器函数的参数
# 根据装饰器的参数做一些逻辑判断
if a:
print("{}您好".format(a))
else:
print("您好")
# 调用被装饰的函数,接收参数args和kwargs
func(*args, **kwargs)
return bar
return foo
@d() # 不给装饰器传参数,使用默认的'None'参数
def wish(name):
print("{}祝您前程似锦".format(name))
@d("亲爱的园友") # 给装饰器传一个参数
def greet_wish(name):
print("{}祝您前程似锦".format(name))
if __name__ == '__main__':
wish("lcg")
print('-' * 50)
greet_wish("lcg")
输出:
您好 lcg祝您前程似锦 -------------------------------------------------- 亲爱的园友您好 lcg祝您前程似锦
类装饰器和装饰类
类装饰器
除了用函数去装饰函数外,我们还可以使用类去装饰函数。
class D(object):
def __init__(self, a=None):
self.a = a
self.mode = "装饰"
def __call__(self, *args, **kwargs):
if self.mode == "装饰":
self.func = args[0] # 默认第一个参数是被装饰的函数
self.mode = "调用"
return self
# 当self.mode == "调用"时,执行下面的代码(也就是调用使用类装饰的函数时执行)
if self.a:
print("{}您好".format(self.a))
else:
print("您好")
self.func(*args, **kwargs)
@D()
def wish(name):
print("{}祝您前程似锦".format(name))
@D("亲爱的园友")
def greet_wish(name):
print("{}您前程似锦".format(name))
if __name__ == '__main__':
wish("lcg")
print('-' * 50)
greet_wish("lcg")
输出:
您好 lcg祝您前程似锦 -------------------------------------------------- 亲爱的园友您好 lcg您前程似锦
装饰类
我们上面所有的例子都是装饰一个函数,返回一个可执行函数。Python中的装饰器除了能装饰函数外,还能装饰类。
可以使用装饰器,来批量修改被装饰类的某些方法:
# 定义一个类装饰器
class D(object):
def __call__(self, cls):
class Inner(cls):
# 重写被装饰类的f方法
def f(self):
print("Hello lcg.")
return Inner
@D()
class C(object): # 被装饰的类
# 有一个实例方法
def f(self):
print("Hello world.")
if __name__ == '__main__':
c = C()
c.f()
输出:
Hello lcg.
装饰器修复技术
官方解释:https://docs.python.org/3/library/functools.html#functools.wraps
from functools import wraps
def deco(func):
@wraps(func) # 加在最内层函数正上方
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@deco
def index():
'''哈哈哈哈'''
x = 10
print('from index')
print(index.__name__)
print(index.__doc__)
# 加wraps
# index
# 哈哈哈哈
# 不加waps
# wrapper
# None

浙公网安备 33010602011771号