装饰器来自
Decorator
的直译。什么叫装饰,就是装点、提供一些额外的功能。在
Python
中的装饰器则是提供了一些额外的功能。
装饰器本质上是一个
Python
函数(其实就是闭包),它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
装饰器用于有以下场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的应用有插入日志、增加计时逻辑来检测性能、加入事务处理等。
装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
一、使用函数定义装饰器
不带参数装饰器
import time
def cost(func):
"""计算运行时间装饰器"""
def wrapper(*args, **kwargs):
s_time = time.time()
result = func(*args, **kwargs)
e_time = time.time()
print({
'args': args,
'kwargs': kwargs,
'result': result,
's_time': s_time,
'e_time': e_time,
'cost': e_time - s_time
return result
return wrapper
@cost
def fibonacci(n: int):
"""斐波那契数列"""
if n <= 2:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)
@cost
def speak():
print('hello world')
if __name__ == '__main__':
print(fibonacci(10))
speak()
{'args': (2,), 'kwargs': {}, 'result': 1, 's_time': 1681352368.4956758, 'e_time': 1681352368.4956758, 'cost': 0.0}
{'args': (1,), 'kwargs': {}, 'result': 1, 's_time': 1681352368.4956758, 'e_time': 1681352368.4956758, 'cost': 0.0}
{'args': (3,), 'kwargs': {}, 'result': 2, 's_time': 1681352368.4956758, 'e_time': 1681352368.4956758, 'cost': 0.0}
{'args': (8,), 'kwargs': {}, 'result': 21, 's_time': 1681352368.511631, 'e_time': 1681352368.5126326, 'cost': 0.001001596450805664}
{'args': (10,), 'kwargs': {}, 'result': 55, 's_time': 1681352368.4956758, 'e_time': 1681352368.5126326, 'cost': 0.016956806182861328}
hello world
{'args': (), 'kwargs': {}, 'result': None, 's_time': 1681359050.2040539, 'e_time': 1681359050.2040539, 'cost': 0.0}
分析:此时的@cost相当于将fibonacci函数的内存地址传入cost函数,并返回wrapper函数的内存地址。因此在代码结尾中调用fibonacci()
,本质上是执行wrapper函数。因为执行的是wrapper函数,而func函数是fibonacci的内存地址,所以调用func,执行fibonacci。
备注:@是语法糖
不用语法糖的情况下,使用下面语句也能实现装饰作用:把fibonacci再加工,再传给fibonacci \
fibonacci = cost(fibonacci)
装饰器带参数
把装饰器再包装,实现传递装饰器参数。
def query(method):
def wrapper(func):
def sub_wrapper(*args, **kwargs):
print(f'查询方式:{method}')
return func(*args, **kwargs)
return sub_wrapper
return wrapper
@query(method='POST')
def fetch(url):
print(f'fetch url: {url}')
if __name__ == '__main__':
fetch(url='https://www.baidu.com')
查询方式:POST
fetch url: https:
分析:带参数的装饰器与普通的装饰器多加了一层,其实就是将python参数传入query函数,并返回wrapper函数的内存地址,
再将fetch函数内存地址传入wrapper函数,并返回了sub_wrapper函数的内存地址。而在代码末尾调用fetch,其实本质是调用了sub_wrapper函数。
二、在类里定义装饰器,装饰本类内函数
类装饰器,装饰函数和类函数调用不同的类函数
把装饰器写在类里
在类里面定义个函数,用来装饰其它函数,严格意义上说不属于类装饰器。
class Logger:
@staticmethod
def info(func):
def wrapper(*args, **kwargs):
print(f'log_type: info'.center(50, '-'))
return func(*args, **kwargs)
return wrapper
def error(func):
def wrapper(*args, **kwargs):
print(f'log_type: error'.center(50, '-'))
return func(*args, **kwargs)
return wrapper
def debug(self, func):
def wrapper(*args, **kwargs):
print(f'log_type: debug'.center(50, '-'))
return func(*args, **kwargs)
return wrapper
@classmethod
def critical(cls, func):
def wrapper(*args, **kwargs):
print(f'log_type: critical'.center(50, '-'))
return func(*args, **kwargs)
return wrapper
@Logger.info
def greet(text):
print(f'greet: {text}')
@Logger.error
def speak(text):
print(f'speak: {text}')
my_logger = Logger()
@my_logger.debug
def tell(text):
print(f'tell: {text}')
@Logger.critical
def eat(food):
print(f'eat: {food}')
if __name__ == '__main__':
greet('hello world')
speak('how are you')
tell('I am fine')
eat('apple')
------------------log_type: info------------------
greet: hello world
-----------------log_type: error------------------
speak: how are you
-----------------log_type: debug------------------
tell: I am fine
----------------log_type: critical----------------
eat: apple
为了让装饰器在使用上更加灵活,我们把类的实例方法作为装饰器,此时在包裹函数中就可以持有实例对象,便于修改属性和拓展功能。
装饰器装饰同一个类里的函数
需求点:想要通过装饰器修改类里的self属性值。
from functools import wraps
class Test(object):
def __init__(self):
self.reset = True
self.flag = True
def info(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
print('log_type: info'.center(50, '-'))
if self.reset:
print('Reset is Ture, change flag...')
self.flag = False
return func(self, *args, **kwargs)
return wrapper
@info
def speak(self, text):
print(f'speak: {text}')
if __name__ == '__main__':
t = Test()
t.speak('hello world')
print(t.flag)
------------------log_type: info------------------
Reset is Ture, change flag...
speak: hello world
False
三、类装饰器
定义一个类装饰器,装饰函数,默认调用__call__方法
class Test:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('函数__call__被调用了'.center(50, '-'))
return self.func(*args, **kwargs)
@Test
def speak(text):
print(f'speak: {text}')
if __name__ == '__main__':
speak('hello world')
------------------函数__call__被调用了------------------
speak: hello world
定义一个类装饰器,装饰类中的函数,默认调用__get__方法
实际上把类方法变成属性了,还记得类属性装饰器@property,下面自已做一个property
class Test:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
instance:代表实例
owner:代表类本身
print('调用的是get函数'.center(50, '-'))
return self.func(instance)
class Person(object):
def __init__(self):
self.result = 0
@Test
def speak(self):
print('speak: hello world')
return True
if __name__ == '__main__':
p = Person()
p.speak
--------------------调用的是get函数---------------------
speak: hello world
做一个求和属性sum,统计所有输入的数字的和
class Decorator:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
print('调用的是get函数')
return self.func(instance)
class Test:
def __init__(self, *args, **kwargs):
self.value_list = []
if args:
for i in args:
if str(i).isdigit():
self.value_list.append(i)
if kwargs:
for v in kwargs.values():
if str(v).isdigit():
self.value_list.append(v)
@Decorator
def sum(self):
result = 0
print(self.value_list)
for i in self.value_list:
result += i
return result
if __name__ == '__main__':
t = Test(1,2,3,4,5,6,7,8,i=9,ss=10,strings = 'lll')
print(t.sum)
编写一个类装饰器,将装饰器应用于所有方法
import inspect
class DecoratedAllMethod:
def __init__(self, func):
self.func = func
def __get__(self, obj, cls=None):
def wrapper(*args, **kwargs):
print("decorate: before".center(50, '-'))
try:
ret = self.func(obj, *args, **kwargs)
except TypeError:
ret = self.func(*args, **kwargs)
print("decorate: after".center(50, '*'))
return ret
for attr in "__module__", "__name__", "__doc__":
setattr(wrapper, attr, getattr(self.func, attr))
return wrapper
class DecoratedInstanceMethod:
def __init__(self, func):
self.func = func
def __get__(self, obj, cls=None):
def wrapper(*args, **kwargs):
print("decorate instance method: before".center(50, '-'))
ret = self.func(obj, *args, **kwargs)
print("decorate instance method: after".center(50, '*'))
return ret
for attr in "__module__", "__name__", "__doc__":
setattr(wrapper, attr, getattr(self.func, attr))
return wrapper
class DecoratedClassMethod:
def __init__(self, func):
self.func = func
def __get__(self, obj, cls=None):
def wrapper(*args, **kwargs):
print("decorate class method: before")
ret = self.func(*args, **kwargs)
print("decorate class method: after")
return ret
for attr in "__module__", "__name__", "__doc__":
setattr(wrapper, attr, getattr(self.func, attr))
return wrapper
def decorate_class(cls):
for name, meth in inspect.getmembers(cls):
if inspect.ismethod(meth) or inspect.isfunction(meth):
setattr(cls, name, DecoratedAllMethod(meth))
return cls
@decorate_class
class Person:
def __init__(self, name):
self.name = name
print("__init__")
def call(self):
print(self.name)
@staticmethod
def speak(text):
print(f"speak: {text}")
@classmethod
def eat(cls):
print("eat...")
if __name__ == '__main__':
p = Person(name='张三')
p.call()
p.speak('hello world')
Person.speak('你好')
p.eat()
Person.eat()
缓存和计时装饰器的综合练习
import time
class CacheDecorator:
"""缓存装饰器"""
__cache = {}
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
if self.func.__name__ in CacheDecorator.__cache:
return CacheDecorator.__cache[self.func.__name__]
else:
result = self.func(*args, **kwargs)
CacheDecorator.__cache[self.func.__name__] = result
return result
def cost_time(func):
"""时间计算装饰器"""
def wrapper(*args, **kwargs):
s_time = time.time()
result = func(*args, **kwargs)
e_time = time.time()
print(f'cost time: {e_time - s_time}')
return result
return wrapper
@cost_time
@CacheDecorator
def test():
"""模拟耗时较长,每次执行返回结果都一样的情况"""
print("start...")
time.sleep(3)
print("end...")
return 999
if __name__ == '__main__':
t1 = test()
t2 = test()
print(t1)
print(t2)
www.fengnayun.com/news/conten…
blog.csdn.net/qq_37189082…
www.zhihu.com/tardis/bd/a…
www.codenong.com/6695854/
www.cnblogs.com/wuxianfeng0…
blog.csdn.net/tyhj_sf/art…