如何不饶的理解Python里装饰器?
@xxx
放在函数/类 定义上面,就是Python里的装饰器语法。先介绍什么是装饰器,再介绍语法含义。
装饰器
一种经典设计模式,可以理解为套娃。把一个东西包裹一层,再暴露给别人,这个包裹的装置就是装饰器。
Python里的装饰器
前面提到, @xxx
放在类/函数定义上就是在Python里应用了装饰器了。它的语法糖拆开如下(举例):
@xxx
def original_fn(a, b):
return a + b
# ==>
original_fn = xxx(original_fn)
如果你要自己写装饰器,xxx
就得满足这样的性质:
- 接受1个参数a,返回1个对象A; a就是被装饰的对象,A就是对a包裹的结果。
- A本身接受的参数应该与a匹配;因为A在后面就替代a了;
上面就是对装饰器的理解,下面再说几个常见的东西:
1. 带参数的装饰器?
就是这样的:
@xxx(a, b)
def original_fn(c, d):
return c + d
这个什么? 搞清楚很简单,xxx(a, b)
本身是一个函数调用!这里xxx
不是一个装饰器,xxx(a, b)
函数调用
返回的才是一个装饰器!把它看作装饰器的构造器比较合适。
如果要全部用函数来定义这样的xxx
,这个xxx
估计是一个3层套娃……
2. 用类实现一个装饰器?
常见装饰器都是拿函数实现的,但是也可以用类来做。只需要满足上面的 @
语法的语法糖就行。
一般地,用类来做装饰器,类的构造函数就接受被装饰对象,类的__call__
就接受与被装饰对象匹配的参数。
用类做装饰器,更加直观,稍微没那么绕,也让人更清楚@
的本质——并没有明确的范式,满足语法糖扩展就行了。
3. 装饰器去装饰一个类?
跟装饰函数一个道理。返回的对象和该类接口兼容就行。
4. functools.wraps 做什么、怎么做?
对象被装饰之后,本质上已经是一个新的对象了,也就不再保留原被装饰对象的属性了,如__doc__
, __name__
等,
这个显然是符合科学的。
然而有时我们偏偏想保留原来被装饰对象的__doc__
等属性——不可以违背科学,那就再装饰一下吧!
我们要知道,对象的__doc__
等是可以赋值修改的! 我们把装饰后的对象,修改其__doc__
等属性为原对象的属性,不就
达到目的了吗。这就是functools.wraps
做的事情。它也是依赖装饰器语法的,真的套娃,难受。
它是为装饰器服务的,与被装饰对象无关。
实际实现似乎比较复杂,有封装,逻辑实现应该是如下?
def wraps(original_fn):
"""it it actual a wrapper creator
"""
def wraps_decorator(original_wraps):
"""a in-out decorator
"""
def nest_wraps(*args, **kargs):
return original_wraps(*args, **kwargs)
nest_wraps.__doc__ == orignal_fn.__doc__
# ...
return nest_wraps
return wraps_decorator
def decorator(original_fn):
@wraps(original_fn)
def actual_wrapper(*args, **kwargs):
# ...
original_fn(*args, **kwargs)
# ..
return actual_wrapper
==>
# 把decorator内部的装饰器展开,其实就是
actual_wrapper = wraps(original_fn)(actual_wrapper)
= wraps_decorator(actual_wrapper)
= nest_wraps
# => 大概是这样的吧,这样包装后actual_wrapper就有了 original_fn 的属性了。我自己又晕了……