1.装饰器实现的基本问题
基本的Python装饰器
@function_wrapper
def function():
pass
以上代码等价于:
def function():
pass
function = function_wrapper(function)
使用类来实现装饰器
class function_wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __call__(self, *args, **kwargs):
return self.wrapped(*args, **kwargs)
@function_wrapper
def function():
pass
本例中的类实例用原始函数对象初始化并记录。当调用被装饰函数时,实际上调用的是装饰器对象的_call__()方法。然后再调用原始包装函数。单独传递对装饰器的调用并不是特别有用,通常希望在调用装饰器函数之前或之后做一些工作。或者可能希望在输入参数时通过装饰器修改函数运行,要到达这个目的只需要适当地修改_call__()方法来做您想做的事情。
通常使用__name__ and __doc__来返回函数的信息,但是当使用装饰器时,函数不再像使用函数闭包时那样工作,而是返回嵌套函数的详细信息。
```python
def function_wrapper(wrapped):
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
return _wrapper
@function_wrapper
def function():
pass
>>> print(function.__name__)
_wrapper
如果使用类来实现装饰器,因为类实例通常没有_name__属性,尝试访问函数名实际上会导致AttributeError异常。
class function_wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __call__(self, *args, **kwargs):
return self.wrapped(*args, **kwargs)
@function_wrapper
def function():
pass
>>> print(function.__name__)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'function_wrapper' object has no attribute '__name__'
使用函数闭包时,解决方案是将感兴趣的属性从wrapper函数复制到nested wrapped函数。这样函数名和文档字符串是正确的。
def function_wrapper(wrapped):
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
_wrapper.__name__ = wrapped.__name__
_wrapper.__doc__ = wrapped.__doc__
return _wrapper
@function_wrapper
def function():
pass
>>> print(function.__name__)
function
需要手动复制属性是很费力的,如果添加了需要复制的任何其他特殊属性,则需要更新这些属性。例如,如果还应该复制__module__属性,并且在python3中添加了__qualname__和__annotations__属性。为了正确实现这一目的,Python标准库提供了functools.wrap()装饰器来完成这一任务。
import functools
def function_wrapper(wrapped):
@functools.wraps(wrapped)
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
return _wrapper
@function_wrapper
def function():
pass
>>> print(function.__name__)
function
如果使用类来实现包装器,则使用functools.update_wrapper()函数。
import functools
class function_wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
functools.update_wrapper(self, wrapped)
def __call__(self, *args, **kwargs):
return self.wrapped(*args, **kwargs)
2.实例讲解
本节实例转载自其他博客
模拟ATM机:
def deposit():
print('存款中.....')
def withdrawal():
print('取款中.....')
button = 1
if button == 1:
deposit()
else:
withdrawal()
存款中.....
不对,存取款要输入密码啊!!!所以,我们要加密码验证代码。
def deposit():
print('存款中.....')
def withdrawal():
print('取款中.....')
def check_password():
print('密码验证中...')
button = 2
if button == 1:
check_password()
deposit()
else:
check_password()
withdrawal()
密码验证中...
取款中.....
可以看到,虽然实现了密码验证功能,但是代码冗余度比较高,而且现在只模拟了取款和存款功能,然而还有查询功能,转账功能等等,那么冗余度就更高了,而且相对于取款和存款函数来说,复用性没有那么高,所以我们要进一步优化代码,把验证函数写到取款和存款函数内部。
def deposit():
print('存款中.....')
def withdrawal():
print('取款中.....')
def check_password(func):
print('密码验证中...')
func()
button = 2
if button == 1:
check_password(deposit)
else:
check_password(withdrawal)
密码验证中...
取款中.....
虽然问题已经解决,但是业务逻辑已经改变,也就是说业务逻辑代码需要重新更改,如果再增加新的功能,代码又需要重新改。有没有什么方法,可以在不改变原函数以及原函数的调用的情况下扩展原函数的功能呢?当然是有的,这就是python中著名的装饰器。还是使用上面的代码来演示。
def check_password(func):
def inner():
print('密码验证中....')
func()
return inner
def deposit():
print('存款中.....')
def withdrawal():
print('取款中.....')
deposit = check_password(deposit)
withdrawal = check_password(withdrawal)
button = 2
if button == 1:
deposit()
else:
withdrawal()
密码验证中....
取款中.....
可以看到,即使原函数和原函数的调用函数都没有发生改变,但是函数的功能却扩展了,可维护新很高,这就是装饰器的强大之处。装饰器运用了闭包的原理。
上述代码可以使用语法糖进一步简化。
def check_password(func):
def inner():
print('密码验证中....')
func()
return inner
@check_password #相当于deposit = check_password(deposit)
def deposit():
print('存款中.....')
@check_password #相当于withdrawal = check_password(withdrawal)
def withdrawal():
print('取款中.....')
button = 2
if button == 1:
deposit()
else:
withdrawal()
下面来看装饰器装饰有参函数的情况:
def celebrator(func):
def inner():
print('我是新增功能')
func()
return inner
@celebrator
def myprint(a):
print(a)
myprint('Python小白联盟')
输出如下:TypeError: inner() takes 0 positional arguments but 1 was given
看报错原因就知道,inner()函数多了一个参数。大家一定要注意一点,因为装饰器函数的返回值是inner,也就是说现在myprint是等同于inner的。我们来改一下代码:
def celebrator(func):
def inner(str):
print('我是新增功能')
func(str)
return inner
@celebrator
def myprint(a):
print(a)
myprint('Python小白联盟')
我是新增功能
Python小白联盟
为了使装饰器能够装饰更多函数,我们进一步优化代码。
def celebrator(func):
def inner(*args,**kwargs):
print('我是新增功能')
func(*args,**kwargs)
return inner
@celebrator
def myprint(a):
print(a)
myprint('Python小白联盟')
装饰器装饰有返回值函数的情况:
def celebrator(func):
def inner(*args,**kwargs):
print('我是新增功能')
func(*args,**kwargs)
return inner
@celebrator
def myprint(a):
return a #带有返回值
@celebrator
def yourprint(b):
print(b)
ret1 = myprint('Python小白联盟')
ret2 = yourprint('666')
print(ret1,ret2)
我是新增功能
我是新增功能
666
None None
可以看到,无论被装饰的函数有无返回值,其结果都无返回值,原因其实很简单,因为inner()函数根本就没有返回值。为了实现有返回值的函数被装饰之后仍然有返回值,需要inner函数与被装饰函数的返回值保持一致。
def celebrator(func):
def inner(*args,**kwargs):
print('我是新增功能')
ret = func(*args,**kwargs)
return ret
return inner
@celebrator
def myprint(a):
return a #带有返回值
@celebrator
def yourprint(b):
print(b)
ret1 = myprint('Python小白联盟')
ret2 = yourprint('666')
print(ret1,ret2)
我是新增功能
我是新增功能
666
Python小白联盟 None
可以看到,有返回值的函数被装饰之后依然有返回值,没有返回值的函数被装饰之后则没有返回值,符合我们想要的结果。
双重语法糖:
为了更加直观地演示双重语法糖,我就不再使用上面的例子了。如我们要实现在输出上面打印一行’=‘和一行’*’:
def printequal(func):
def inner():
print('='*15)
func()
return inner
def printstar(func):
def inner():
print('*'*15)
func()
return inner
@printequal
@printstar
def myprint():
print('python小白联盟')
myprint()
===============
***************
python小白联盟
def printequal(func):
def inner():
print('='*15)
func()
return inner
def printstar(func):
def inner():
print('*'*15)
func()
return inner
@printstar
@printequal
def myprint():
print('python小白联盟')
myprint()
***************
===============
python小白联盟
可以看到,叠加的方式应该是第一种,也就是说最外层的语法糖先执行。
带参数的语法糖
def get_celebrator(char):
def print_style(func):
def inner():
print(char*15)
func()
return inner
return print_style
@get_celebrator('=')
@get_celebrator('*')
@get_celebrator('-')
def myprint():
print('python小白联盟')
myprint()
===============
***************
---------------
python小白联盟