装饰器讲解
装饰器(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) 