一、基础用法
1.1、函数添加装饰器
不会修改原始函数的参数签名和返回值。使用用*args和 **kargs的目的是确保任何参数都能适用。装饰器类似一个AOP程序
import time
from functools import wraps
def time_use(func):
(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) #func就是原始的函数count_down
end = time.time()
print(f'func name: {func.__name__}, time use: {end - start} s') #注意这里的__name__写法
return result
return wrapper
def count_down(n):
while n > 0:
n -= 1
count_down(100000)# func name: count_down, time use: 0.005406856536865234 s
count_down(10000000)# func name: count_down, time use: 0.5120158195495605 s
1.2、函数解除装饰器
import time
from functools import wraps
def time_use(func):
(func)
def wrapper1(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) #func就是原始的函数count_down
end = time.time()
print(f'func name: {func.__name__}, time use: {end - start} s') #注意这里的__name__写法
return result
return wrapper1
def count_down(n):
while n > 0:
n -= 1
count_down(100000)# func name: count_down, time use: 0.005406856536865234 s
count_down(10000000)# func name: count_down, time use: 0.5120158195495605 s
def add(x, y):
return x + y
#用了这行以后就解除了装饰器,但必须要调用一次否则是不生效的,比如在下面代码中插入
orig_add = add.__wrapped__
# add(3,5) #这行还是会执行wraps代码
print(f'add result: {orig_add(3, 5)}') #add result: 8
#这种写法也可以
print(f'add result: {add.__wrapped__(3, 5)}')
1.3、得到函数元信息
print(f'func name: {count_down.__name__}')
print(f'doc is: {count_down.__doc__}')
print(f'annotations is: {count_down.__annotations__}')
from inspect import signature
print(f'signature: {signature(count_down)}')#signature: (*args, **kwargs)
1.4、在类中定义装饰器
这样装饰器可以同时在类的内部和外部一起使用了。
class A:
# Decorator as an instance method
def decorator_1(self, func):
(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper
# Decorator as a class method
def decorator_2(cls, func):
(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper
# As an instance method
a = A()
.decorator_1
def spam():
pass
# As a class method
.decorator_2
def grok():
pass
class B(A):
.decorator_2
def bar(self):
pass
class Profiled:
def __init__(self, func):
wraps(func)(self)
self.ncalls = 0
def __call__(self, *args, **kwargs):
self.ncalls += 1
return self.__wrapped__(*args, **kwargs)
#确保绑定方法对象能正确的创建,types.MethodType创建一个绑定方法,只有当实例被使用时才会被创建。
def __get__(self, instance, cls):
if instance is None:
return self
else:
return types.MethodType(self, instance)
def add(x, y):
return x + y
class Spam:
def bar(self, x):
print(f'object: {self}, param is: {x}')
print(f'number add result: {add(3, 5)}')
print(f'number add result: {add(5, 8)}')
print(f'ncalls: {add.ncalls}')
# number add result: 8
# number add result: 13
# ncalls: 2
s = Spam()
s.bar(1)
s.bar(2)
s.bar(3)
print(f'bar ncalls: {Spam.bar.ncalls}')
# object: <__main__.Spam object at 0x10d334f70>, param is: 1
# object: <__main__.Spam object at 0x10d334f70>, param is: 2
# object: <__main__.Spam object at 0x10d334f70>, param is: 3
# bar ncalls: 3
s = Spam()
def grok(self, x):
pass
print(f'grok get: {grok.__get__(s, Spam)}')
# grok get: <bound method grok of <__main__.Spam object at 0x10d334f70>>
1.5、运行时设置装饰器参数
from functools import wraps, partial
import logging
# Utility decorator to attach a function as an attribute of obj
def attach_wrapper(obj, func=None):
if func is None:
return partial(attach_wrapper, obj)
setattr(obj, func.__name__, func)
return func
def logged(level, name=None, message=None):
'''
Add logging to a function. level is the logging
level, name is the logger name, and message is the
log message. If name and message aren't specified,
they default to the function's module and name.
'''
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__
(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
# Attach setter functions
(wrapper)
def set_level(newlevel):
nonlocal level #修改函数内部的变量
level = newlevel
(wrapper)
def set_message(newmsg):
nonlocal logmsg
logmsg = newmsg
return wrapper
return decorate
# Example use
(logging.DEBUG)
def add(x, y):
return x + y
(logging.CRITICAL, 'example')
def spam():
print('Spam!')
import logging
logging.basicConfig(level=logging.DEBUG)
print(f'add result: {add(3, 5)}')
add.set_message('Add called') #修改函数内部变量
print(f'add result: {add(3, 5)}')
add.set_level(logging.WARNING)
print(f'add result: {add(3, 5)}')
二、参数相关
2.1、带参数的装饰器
from functools import wraps
import logging
def logged(level, name=None, message=None):
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__
(func)
def wrapper(*args, **kwargs):
log.log(level, message)
return func(*args, **kwargs)
return wrapper
return decorate
# Example use
(logging.CRITICAL)
def add(x, y):
return x + y
add(1, 2) #add
(logging.CRITICAL, 'example')
def spam():
print('Spam!')
spam() #spam
2.2、带可选参数的装饰器
from functools import wraps, partial
import logging
def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
if func is None:
return partial(logged, level=level, name=name, message=message)
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__
(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
# Example use
def add(x, y):
return x + y
(level=logging.CRITICAL, name='example')
def spam():
print('Spam!')
三、装饰器使用
3.1、函数类型检查
from inspect import signature
from functools import wraps
def type_assert(*ty_args, **ty_kwargs):
def decorate(func):
# If in optimized mode, disable type checking
if not __debug__:
return func
# Map function argument names to supplied types
sig = signature(func)
#从指定类型到名称的部分绑定,它与bind的区别是不允许忽略任何参数
bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments
(func)
def wrapper(*args, **kwargs):
bound_values = sig.bind(*args, **kwargs)
# Enforce type assertions across supplied arguments
for name, value in bound_values.arguments.items():
if name in bound_types:
if not isinstance(value, bound_types[name]):
raise TypeError(f'Argument {name} must be {bound_types[name]}')
return func(*args, **kwargs)
return wrapper
return decorate
(int, int)
def add(x, y):
return x + y
print(f'add result: {add(2, 3)}')
# add(2, 'hello')
(int, z=int)
def spam(x, y, z=42):
print(f'x = {x}, y = {y}, z = {z}')
spam(1, 2, 3)
spam(1, 'hello', 3)
# spam(1, 'hello', 'world')
def decorate(func):
# If in optimized mode, disable type checking
if not __debug__:
return func
from inspect import signature
def spam(x, y, z=42):
pass
sig = signature(spam)
print(f'sig = {sig}')
print(f'parameters: {sig.parameters}')
print(f'parameters z name = {sig.parameters["z"].name}')
print(f'parameters z default = {sig.parameters["z"].default}')
print(f'parameters z kind = {sig.parameters["z"].kind}')
bound_types = sig.bind_partial(int, z=int)
print(f'bound_types = {bound_types}')
print(f'bound_types arguments = {bound_types.arguments}')
bound_values = sig.bind(1, 2, 3)
print(f'arguments = {bound_values.arguments}')
for name, value in bound_values.arguments.items():
if name in bound_types.arguments:
if not isinstance(value, bound_types.arguments[name]):
raise TypeError
(int, list)
def bar(x, items=None):
if items is None:
items = []
items.append(x)
return items
print(f'bar single param: {bar(3)}')
# print(f'bar double param: {bar(3, 5)}')
print(f'bar mix param: {bar(4, [1, 2, 3])}')
def spam(x:int, y, z:int = 42):
print(x,y,z)
add result: 5
x = 1, y = 2, z = 3
x = 1, y = hello, z = 3
sig = (x, y, z=42)
parameters: OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">), ('z', <Parameter "z=42">)])
parameters z name = z
parameters z default = 42
parameters z kind = POSITIONAL_OR_KEYWORD
bound_types = <BoundArguments (x=<class 'int'>, z=<class 'int'>)>
bound_types arguments = {'x': <class 'int'>, 'z': <class 'int'>}
arguments = {'x': 1, 'y': 2, 'z': 3}
bar single param: [3]
bar mix param: [1, 2, 3, 4]
3.2、给函数添加参数
from functools import wraps
def optional_debug(func):
(func)
def wrapper(*args, debug=False, **kwargs):
if debug:
print('Calling', func.__name__)
return func(*args, **kwargs)
return wrapper
def spam(a, b, c):
print(f'a = {a}, b = {b}, c = {c}')
spam(1, 2, 3)
spam(1, 2, 3, debug=True)
# a = 1, b = 2, c = 3
# Calling spam
# a = 1, b = 2, c = 3
from functools import wraps
import inspect
def optional_debug(func):
if 'debug' in inspect.getfullargspec(func):
raise TypeError('debug argument already defined')
(func)
def wrapper(*args, debug=False, **kwargs):
if debug:
print(f'Calling {func.__name__}')
return func(*args, **kwargs)
sig = inspect.signature(func)
parms = list(sig.parameters.values())
parms.append(inspect.Parameter('debug',
inspect.Parameter.KEYWORD_ONLY,
default=False))
wrapper.__signature__ = sig.replace(parameters=parms)
return wrapper
def add(x,y):
return x+y
print(f'signature: {inspect.signature(add)}')
print(f'add result: {add(5,3)}')
# signature: (x, y, *, debug=False)
# add result: 8
3.3、扩充类的功能
def log_getattribute(cls):
# Get the original implementation
orig_getattribute = cls.__getattribute__
# Make a new definition
def new_getattribute(self, name):
print(f'getting name: {name}')
return orig_getattribute(self, name)
# Attach to the class and return
cls.__getattribute__ = new_getattribute
return cls
# Example use
class A:
def __init__(self,x):
self.x = x
def spam(self):
pass
a = A(30)
print(f'a.x = {a.x}')
print(f'a.spam(): {a.spam()}')
# getting name: x
# a.x = 30
# getting name: spam
# a.spam(): None
class LoggedGetattribute:
def __getattribute__(self, name):
print(f'getting name: {name}')
return super().__getattribute__(name)
# Example:
class A(LoggedGetattribute):
def __init__(self,x):
self.x = x
def spam(self):
pass