装饰器
装饰器可以非侵入地完成对方法、类的封装。装饰器的返回值也是一个方法、类对象。装饰器常用来完成以下工作
- 插入日志logging
- 性能测试pysnooper
- 事务处理
- 缓存
- 权限校验
- 格式转换dict -> Json
从装饰器本身看,装饰器可以是一个返回方法的方法,也可以是一个类对象
从装饰器装饰的对象看,装饰器可以装饰一个方法,也可以装饰一个对象
方法装饰器
简单装饰器
def use_logging(func):
def wrapper():
print("%s is running" % func.__name__)
return func()
return wrapper
@use_logging
def foo():
print("im foo")
foo()
被装饰带参数的方法
被装饰的方法有确定的参数name
def use_logging(func):
def wrapper(name):
print("%s is running" % func.__name__)
return func(name)
return wrapper
@use_logging
def foo(name):
print("my name is %s" % name)
foo('hankin')
被装饰的方法有不确定的参数
def use_logging(func):
def wrapper(*args):
print("%s is running" % func.__name__)
return func(*args)
return wrapper
@use_logging
def foo(fname, lname):
print("my name first name is %s, last name is %s" % (fname, lname))
foo('hankin', 'cheung')
被装饰的方法有关键字
参数
def use_logging(func):
def wrapper(*args, **kwargs):
print("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
@use_logging
def foo(fname='hankin', lname='cheung'):
print("my first name is %s, last name is %s" % (fname, lname))
foo(fname='leiffy', lname='lee')
装饰器带参数
带参数的装饰可以是log中的level等级,缓存时长等
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == 'warn':
logging.warn('%s is running' % func.__name__)
elif level == 'info':
logging.info('%s is running' % func.__name__)
return func(*args, **kwargs)
return wrapper
return decorator
@use_logging(level='warn')
def foo(name='foo'):
print('i am %s' % name)
foo()
类实现的装饰器
类实现的类装饰器主要依靠类的call方法
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
类装饰器
类装饰器可以用于装饰类的某些方法和属性,下面的例子用于装饰类的__getattribute__
方法
def log_getattribute(cls):
# Get the original implementation
orig_getattribute = cls.__getattribute__
# Make a new definition
def new_getattribute(self, name):
print('getting:', name)
return orig_getattribute(self, name)
# Attach to the class and return
cls.__getattribute__ = new_getattribute
return cls
# Example use
@log_getattribute
class A:
def __init__(self,x):
self.x = x
def spam(self):
pass
a = A(1)
a.x
a.spam()
有些类装饰器可以控制类的初始化,比如单例模式
def singleton(cls, *args, **kwargs):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
class test:
pass
@singleton
class test2:
pass
a = test()
b = test()
print(id(a), id(b))
c = test2()
d = test2()
print(id(c), id(d))
装饰器和元编程
wraps注解
上面介绍的装饰器实现简单,但是在装饰以后会丢失一些元信息,如doc, anotations,用functools包中的@wraps(func)
注解实现会避免这个问题,下面是一个打印执行时间的装饰器
import time
from functools import wraps
def timethis(func):
'''
Decorator that reports the execution time.
'''
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end-start)
return result
return wrapper
@timethis
def countdown(n):
'''
Counts down
'''
while n > 0:
n -= 1
print(countdown.__doc__)
print(countdown.__annotations__)
print(countdown.__wrapped__)
如果没用@wraps(func)
实现,会丢失元信息
None
{}
除此之外,@wraps(func)
有重要的特性
- 可以通过
countdown.__wrapped__(10000)
直接访问被包装后的函数 - 以及拿到函数的签名信息
from inspect import signature
print(signature(countdown))
Counts down
{}
<function countdown at 0x10673d268>
(n)
(py3.7)
解除一个装饰器
如果一个函数已经被封装,现在想解除装饰器(1个或多个)
通过上面提到的__wrapped__
属性即可
from functools import wraps
def decorator1(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper
def decorator2(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper
@decorator1
@decorator2
def add(x, y):
return x + y
func = add.__wrapped__.__wrapped__
print(func(1,2))
注意: 解除顺序可能会因为python版本的不同而不同,此外如果内部有未使用
@wraps
注解实现的装饰器,执行会出错,例如类内置的装饰器@statidmethod
和@classmethod
,他们把函数的原始属性放在__func__
中
更高阶的装饰器用法