一. 理解和实现装饰器
所谓装饰器, 就是在代码执行期间, 在不改变原有代码(类或者函数)的情况下, 为之动态附加功能.
例如, 在调用函数之前做一些计算, 在函数调用之后输出日志.
如何实现一个装饰器呢, 这里就需要使用到前面学习的知识闭包函数了.
1. 装饰器的原型
import time def decorator(func): # 将原函数作为参数传入 def newfunc(): # 调用之前 stime = time.perf_counter() func() # 调用原函数 # 调用之后 etime = time.perf_counter() print(‘called %s, used %s‘ % (func.__name__, etime-stime)) # 将新函数返回 return newfunc def func(): print("原始函数被调用了") func = decorator(func) # 手动的把新函数赋值给旧函数 func() # newfunc()
2. 装饰器的 @语法糖
在装饰器原型中, 我们是手动的调用 expand 函数, 而在 python 中, 应该使用 @语法糖, 将装饰器 decorator 至于函数的定义处:
@decorator # 相当于 func = expand(func) def func(): print("原始函数被调用了")
这时候, 我们就可以这样调用了
func()
3. 被装饰函数带有参数的装饰器
如果一个需要被装饰的函数有参数, 那么, 在装饰器的返回函数中, 也应该有有相应的参数, 否则会产生错误
import time def decorator(func): # 将原函数作为参数传入 def newfunc(arg, *args, **kwargs): # 调用之前 stime = time.perf_counter() func(arg, *args, **kwargs) # 调用原函数 # 调用之后 etime = time.perf_counter() print(‘called %s, used %s‘ % (func.__name__, etime-stime)) # 将新函数返回 return newfunc @decorator def func(arg, *args, **kwargs): print("原始函数被调用了") func(‘‘) # newfunc()
4. 带有返回值的装饰器函数
import time def decorator(func): def newfunc(arg, *args, **kwargs): # stime = time.perf_counter() return func(arg, *args, **kwargs) # 执行原函数, 并返回原函数的返回值 # etime = time.perf_counter() # return etime-stime # 给原函数添加返回值功能 return newfunc @decorator def func(arg, *args, **kwargs): return "原始函数被调用了" print(func(‘‘)) # 0.0005786
5. 装饰器需要传参数
如果装饰器本身需要传入参数, 那么就要再嵌套一层闭包
import time def log(name): def decorator(func): def wrapper(*args, **kwargs): print(‘%s call %s, on %s‘ % (name, func.__name__, time.strftime("%Y.%m.%d %H:%M:%S"))) return func(*args, **kwargs) # 执行原函数, 并返回原函数的返回值 return wrapper # 实际返回函数 return decorator @log(‘trent‘) # 相当于执行了 func = log(‘trent‘)(func) def func(): print("原始函数被调用了") func()
6. 原函数丢失, 用 wraps 找回
在以上的案例中, 我们会发现, 原函数被新函数替代, 原函数的签名__name__丢了, 文档__doc__等等也丢了, 取而代之的是返回的新函数的东西
def decorator(func): def wrapper(*args, **kwargs): pass return wrapper @decorator def func(): pass print(func.__name__) # wrapper
而我们需要的是原函数, 而不是新的函数, 所以需要使用 functools.wraps 来将原函数的东西赋值给新的函数, 那么我们以上的例子就应该写为:
import time from functools import wraps def log(name): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(‘%s called %s on %s‘ % (name, func.__name__, time.strftime("%Y.%m.%d %H:%M:%S"))) return func(*args, **kwargs) # 执行原函数, 并返回原函数的返回值 return wrapper # 实际返回函数 return decorator
7. 装饰器的优化 @decorator
多层嵌套的装饰器, 不是很直观, 甚至很复杂, 那么我们就需要第三方库来优化, 让装饰器更具有可读性.
使用内置的装饰器: @decorator, 基本能够满足需求
import time from decorator import decorator def log(name): @decorator def dcrt(func, *args, **kwargs): print(‘%s called %s on %s‘ % (name, func.__name__, time.strftime("%Y.%m.%d %H:%M:%S"))) return func(*args, **kwargs) return dcrt @log(‘trent‘) def func(): pass func()
被装饰之后, 我们去查看一下它的源码:
import inspect print(inspect.getsource(func))
执行结果(是原函数的源码):
@log(‘trent‘) def func(): pass
8. 一个强大的装饰器包 wrapt
wrapt 比较完善, 而且能够实现平常没有想到的装饰器, 使用的原型:
import wrapt @wrapt.decorator def pass_through(wrapped, instance, args, kwargs): return wrapped(*args, **kwargs) @pass_through def function(): pass
这里需要注意它的四个参数, 这四个参数是必须要有的, 而且 args 和 kwargs 不带星号, 在返回之时才带星号
由于小弟功力尚浅, 很少用 wrapt 这样的装饰器, 对此暂不做深入的演示, 如果你有兴趣, 可参考 wrapt 的文档: http://wrapt.readthedocs.io/en/latest/quick-start.html
由于在 python 中, 一切皆为对象, 函数形式的装饰器与类形式的装饰器相差不大, 所以对类的装饰器不做赘述.
二. 常用的内置装饰器
以上内容的装饰器优化中, 使用的 @decorator 装饰器, 是其中的一个内置装饰器. 以下将介绍更多的常用的内置装饰器
在此, 我们想看看这样一个简单的类
class Person(): def __init__(self, name): self.__name = name def getName(self): return self.__name def setName(self, val): self.__name = val def delName(self): del self.__name name = property(getName, setName, delName, ‘a document‘)
在此类中, 封装了一个私有属性__name, 并定义了对__name属性的相关操作函数, 那么在我们没有使用 property 之前, 只能通过调用相应的方法才能对属性__name进行相关的操作.
那么我们使用了 property 之后, 我们就可以在外部这样操作:
ps = Person(‘Trent‘) print(ps.name) # 相当于调用了ps.getName()函数 ps.name = ‘_trent_‘ # 相当于调用了ps.setName(‘_trent_‘)函数 print(ps.name) del ps.name # 相当于调用了ps.delName()函数
那么当变量不断的增多时, 我们就要不断的增加一套一套的 get/set/del 和 property , 这样就会显得臃肿, 啰嗦, 而且有些私有的属性我们没有必要设置一套 get/set/del . 因此可以使用以下几个内置的装饰器来解决
1. @property 属性的获取
2. @setter 属性的赋值
3. @deleter 属性的删除
了解至此, 我们可以把以上的 Person 类改写为这样:
class Person(): def __init__(self, name): self.__name = name @property def name(self): return self.__name @name.setter # 注意: setter必须带上前缀 公有的属性名. def name(self, val): self.__name = val @name.deleter # 注意: deleter必须带上前缀 公有的属性名. def name(self): del self.__name
4. @classmethod
5. @staticmethod
class Person(): def run(): print(‘普通方法 run, 只能类调用‘) def walk(self): print(‘实例绑定方法 walk, 自动传递实例对象self参数‘) @classmethod def smile(cls): print(‘类绑定方法 smile, 自动传递类cls参数‘) @staticmethod def look(): print(‘静态方法 look, 无论类和实例对象, 都能调用的方法‘)
在外部使用
ps = Person() # ps.run() # 不能用实例对象调用 Person.run() ps.walk() # Person.walk() # 不能使用类调用 ps.smile() Person.smile() ps.look() Person.look()
总结: 类可以调用的方法有 静态方法, 类绑定方法和 普通方法. 实例可以调用的方法有 实例绑定方法, 类绑定方法 和 静态方法.
原文地址:https://www.cnblogs.com/trent-fzq/p/10989489.html