装饰器
装饰器来自
Decorator
的直译。什么叫装饰,就是装点、提供一些额外的功能。在Python
中的装饰器则是提供了一些额外的功能。 装饰器本质上是一个Python
函数(其实就是闭包),它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。 装饰器用于有以下场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的应用有插入日志、增加计时逻辑来检测性能、加入事务处理等。 装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。 概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
一、使用函数定义装饰器
不带参数装饰器
# coding: utf-8
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}
55
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)
装饰器带参数
把装饰器再包装,实现传递装饰器参数。
# coding: utf-8
# 带参数的装饰器
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://www.baidu.com
分析:带参数的装饰器与普通的装饰器多加了一层,其实就是将
python
参数传入query
函数,并返回wrapper
函数的内存地址, 再将fetch
函数内存地址传入wrapper
函数,并返回了sub_wrapper
函数的内存地址。而在代码末尾调用fetch
,其实本质是调用了sub_wrapper函数。
二、在类里定义装饰器,装饰本类内函数
类装饰器,装饰函数和类函数调用不同的类函数
把装饰器写在类里
在类里面定义个函数,用来装饰其它函数,严格意义上说不属于类装饰器。
# coding: utf-8
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
属性值。
# coding: utf-8
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__方法
# coding: utf-8
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()
缓存和计时装饰器的综合练习
# coding: utf-8
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)