又是从做铺垫开始:
*文档字符串(Documentation Strings)
def add(x, y): """This is a function for add""" # 函数语块第一句;一般用三引号(因为习惯是多行的文本);可以使用.__doc__访问;惯例是首写是大写字母; return x+y print("name={} doc={}".format(add.__name__, add.__doc__)) # 打印出: add(x, y)
doc=This is a function of addition
在装饰器调用过程中,我们打印出这些函数属性信息会发现问题:
def logger(fn): def wrapper(*args, **kwargs): """I am wrapper""" print('begin') x = fn(*args, **kwargs) print('end') return x return wrapper @logger # add = logger(add) def add(x, y): """This is a function for add""" return x+y print("name={} doc={}".format(add.__name__, add.__doc__)) # 打印出: name=wrapper doc=I am wrapper
我们打印出的add()函数的属性并不是add()函数定义时的属性信息,而是wrapper()函数里的属性信息。(因为@logger里将add重新赋值为wrapper)
原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性。怎么解决这个矛盾? ----> 提供一个函数,将被装饰函数的属性拷贝到装饰函数的属性内。
# 将原函数的__name__,__doc__属性赋值到新函数的属性值内 def copy_properties(src, dst): dst.__name__ = src.__name__ dst.__doc__ = src.__doc__ def logger(fn): def wrapper(*args, **kwargs): """I am wrapper""" print('begin') x = fn(*args, **kwargs) print('end') return x copy_properties(fn, wrapper) return wrapper @logger # add = logger(add) def add(x, y): """This is a function for add""" return x+y print("name={} doc={}".format(add.__name__, add.__doc__)) # 打印出: name=add doc=This is a function for add
通过copy_properties()函数,将被装饰函数的属性赋值给装饰函数。在刚了解完装饰器是对原函数起着增加其额外的功能的作用之后,我们是不是会想到这个copy_properties()函数也可以改造成一个装饰
器?
copy_properties()函数 --柯里化--> 加上语法糖形成装饰器
柯里化:
# copy_properties柯里化 def copy_properties(src): def _copy(dst): dst.__name__ = src.__name__ dst.__doc = src.__doc__ return dst return _copy # 被装饰的logger函数 def logger(fn): def wrapper(*args, **kwargs): """I am wrapper""" print('begin') x = fn(*args, **kwargs) print('end') return x copy_propertites(fn)(wrapper) # 柯里化后copy_propertites的调用 return wrapper # 调用 @logger # add = logger(add) def add(x, y): """This is a function for add""" return x+y print("name={} doc={}".format(add.__name__, add.__doc__)) # 打印出 name=add doc=This is a function for add
可以看见这里的柯里化会有些许不同的地方,之前是logger(fn)(*args, **kwargs),现在是copy_properties(fn)(wrapper)。之前是传入函数和函数的参数,现在是分别传入两个函数。
于是就形成了特殊的带参装饰器。
# copy_properties柯里化 def copy_properties(src): def _copy(dst): dst.__name__ = src.__name__ dst.__doc = src.__doc__ return dst return _copy # 被装饰的logger函数 def logger(fn): @copy_properties(fn) => copy_propertites(fn)(wrapper) # 带参装饰器 def wrapper(*args, **kwargs): """I am wrapper""" print('begin') x = fn(*args, **kwargs) print('end') return x return wrapper # 调用 @logger # add = logger(add) def add(x, y): """This is a function for add""" return x+y print("name={} doc={}".format(add.__name__, add.__doc__)) # 打印出 name=add doc=This is a function for add
根据以上归纳总结带参装饰器的特点:
它是一个函数(copy_properties),
函数作为它的形参(fn),
返回值是一个不带参的装饰器函数(wrapper),
使用@function_name(参数列表)语法糖方式调用(@copy_properties(fn))。
可以看作在装饰器外层又加了一层函数。
上述的利用带参装饰器保持被装饰函数的属性不变,在python中functools模块就是这样处理的。有兴趣的可以详细了解一下functools模块。