装饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数。也有利于代码解耦。他们有助于让我们的代码更简短。装饰器让你在一个函数的前后去执行代码。
装饰器的底层实现here是基于闭包。
1. 装饰器基础--函数装饰函数
1.1. 函数、对象和变量
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
def now(): print("2020-01-30") f = now # 函数赋值给变量 f() # 通过变量调用函数
1.2. 一个简单的装饰器
装饰器就是一个返回函数的高阶函数。也就是接收一个函数作为参数。
log是一个装饰器。now是被装饰的函数,log可以用来给now增加功能。
def log(func): # 接受函数作为参数 def wrapper(*args, **kwargs): print("call %s" % func.__name__) return func(*args, **kwargs) return wrapper # 返回一个函数 @log def now(): print("2020-01-30") now()
out: call now 2020-01-30
@log放在now函数上面,相当于执行了now = log(now)
1.3. 带参数的装饰器
如果装饰器需要传入参数,此时就需要先传入参数,之后传入函数,因此需要再封装一层。
def log(text): def decorator(func): def wrapper(*args, **kwargs): print("%s, %s" % (text, func.__name__)) return func(*args, **kwargs) return wrapper return decorator @log('execute') # 这里传入参数 def now(): print("2020-01-30") now()
此时,相当于now = log('execute')(now)
1.4. 被装饰的函数带参数的时候
def log(func): # 接受函数作为参数 def wrapper(name, age): print("call %s" % func.__name__) print(name, age) return func() return wrapper # 返回一个函数 @log def now(): print("2020-01-30") now('Tom', 20) # 此时会调用wrapper函数,然后将参数传递进去。接着执行wrapper方法体,然后执行func()函数,然后返回func()函数的返回值
call now Tom 20 2020-01-30
1.5. 解决函数签名变化的问题
上面不带参数和带参数的装饰器中,通过调用now.name发现返回值为wrapper,这是因为now=log(now),now=log('execute')(now)的返回值是wrapper函数,因此,有些依赖函数签名的代码执行会发生错误。
functools.wraps可以解决这个问题。
import functools def log(func): # 接受函数作为参数 @functools.wraps(func) def wrapper(*args, **kwargs): print("call %s" % func.__name__) return func(*args, **kwargs) return wrapper # 返回一个函数 @log def now(): print("2020-01-30") now() now.__name__
import functools def log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print("%s, %s" % (text, func.__name__)) return func(*args, **kwargs) return wrapper return decorator @log('execute') def now(): print("2020-01-30") now() now.__name__
functools.wraps 旨在消除装饰器对原函数造成的影响,即对原函数的相关属性进行拷贝,已达到装饰器不修改原函数的目的。
2. 函数装饰类
使用函数装饰类实现单实例模式。
from functools import wraps def singleton(cls): instances = {} @wraps(cls) def wrapper(*args, **kargs): if cls not in instances: instances[cls] = cls(*args, **kargs) return instances[cls] return wrapper @singleton class Foo(): pass foo1 = Foo() foo2 = Foo() print(id(foo1)) print(id(foo2))
其中,@singleton相当于Foo = singleton(Foo),函数装饰类的参数是类。id() 函数用于获取对象的内存地址,通过id可以获取对象的内存地址,从而判断出是否是同一个对象。上面的两个输出结果是一样的。
3. 类装饰函数
class Counter: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 return self.func(*args, **kwargs) @Counter # foo = Counter(foo),此时的foo相当于类Counter的实例。 def foo(): pass for i in range(5): foo() # 调用实例对象,此时会执行__call__()方法。 print(foo.count)