Python装饰器详解
装饰器的作用与原理
相当于java中的注解
我们可以在不入侵一个函数的情况下,在这个函数前和函数后,执行一些预设的功能。通常我们会把函数中都会用到的某一类功能(如:鉴权、日志、参数校验等)封装成一个装饰器,这样代码更简洁,语义也更清晰。
在python中用@ 装饰器函数,简化了上述设计模式的使用过程
我们来看一个简单应用
def log(fn):
def wrap_function():
print('准备运行函数fn')
fn()
print('函数fn运行结束')
return wrap_function
@log
def fnA():
print('正在运行fnA....')
fnA()
# 输出:
'''
准备运行函数fn
正在运行fnA....
函数fn运行结束
'''
与下面的代码是等价的
def log(fn):
def wrap_function():
print('准备运行函数fn')
fn()
print('函数fn运行结束')
return wrap_function
def fnA():
print('正在运行fnA....')
b = log(fnA)
b()
1. log() 作为装饰器函数,封装了通用的业务逻辑,去装饰 fnA()
2. @log 相当于简化了装饰器函数的赋值步骤:log(fnA),代码阅读时的业务逻辑更简洁。
使用扩展
- 在装饰器函数中 return wrap_function,返回是必须的吗? 从上述的代码中,我们看到:装饰器函数要被赋值给一个变量,并最终被执行的。所以装饰器函数需要返回一个可调用对象callable。所以不返回,返回整数字符串等,程序都是会报错的。 可以返回一个不相干的函数,程序不会报错。当然这样不推荐。
- 在装饰函数中,如何调用原函数的入参?
def log(fn):
def wrap_function(a):
print('原函数入参...', a)
print('准备运行函数fn')
fn(a)
print('函数fn运行结束')
return wrap_function
@log
def fnA(a):
print('正在运行fnA...', a)
fnA('入参A')
# 输出:
'''
原函数入参... 入参A
准备运行函数fn
正在运行fnA... 入参A
函数fn运行结束
'''
- 接受任意数量入参
def log(fn):
def wrap_function(*args,**kwargs):
print('原函数入参...', args)
print('准备运行函数fn')
fn(*args,**kwargs)
print('函数fn运行结束')
return wrap_function
@log
def fnA(a, b):
print('正在运行fnA...', a, b)
fnA('入参A', '入参B')
# 输出:
'''
原函数入参... ('入参A', '入参B')
准备运行函数fn
正在运行fnA... 入参A 入参B
函数fn运行结束
'''
函数装饰器嵌套执行顺序
先由上到下执行before,执行完原函数后,再由下往上执行After函数
def deA(fn):
def wrap_function(*args, **kwargs):
print('deA原函数入参...', args)
print('deA准备运行函数fn')
fn(*args, **kwargs)
print('deA函数fn运行结束')
return wrap_function
def deB(fn):
def wrap_function(*args, **kwargs):
print('deB原函数入参...', args)
print('deB准备运行函数fn')
fn(*args, **kwargs)
print('deB函数fn运行结束')
return wrap_function
def deC(fn):
def wrap_function(*args, **kwargs):
print('deC原函数入参...', args)
print('deC准备运行函数fn')
fn(*args, **kwargs)
print('deC函数fn运行结束')
return wrap_function
@deA
@deB
@deC
def fnA(a, b):
print('正在运行fnA...', a, b)
fnA('入参A', '入参B')
# 输出:
'''
deA原函数入参... ('入参A', '入参B')
deA准备运行函数fn
deB原函数入参... ('入参A', '入参B')
deB准备运行函数fn
deC原函数入参... ('入参A', '入参B')
deC准备运行函数fn
正在运行fnA... 入参A 入参B
deC函数fn运行结束
deB函数fn运行结束
deA函数fn运行结束
'''
带参数的装饰器
在上述基本的装饰器的基础上,在外面套一层接收参数的函数,来实现装饰器带参数功能
def logger(msg=None):
def out_wrapper(fn):
def wrap_function(*args, **kwargs):
print('装饰器的参数:', msg)
print('准备运行函数fn')
fn(*args, **kwargs)
print('函数fn运行结束')
return wrap_function
return out_wrapper
@logger(msg='hi')
def fnA(a):
print('正在运行fnA....', a)
fnA('入参A')
# 输出:
'''
装饰器的参数: hi
准备运行函数fn
正在运行fnA.... 入参A
函数fn运行结束
'''
Python常用的内置装饰器
@staticmethod、 @classmethod、 @property、@abstractmethod
- 静态方法 @staticmethod
将函数转成静态方法
class B:
@staticmethod
def add(a, b):
return a + b
B.add(10, 5) # 15
- 类方法 @classmethod
将函数转成类方法
class B:
@classmethod
def add(cls):
print('This is class method')
B.add() # This is class method
- 属性 @property
将函数转成只读属性,可以防止被外部修改
class B:
def __init__(self):
self._var1 = 3
self.var2 = 5
@property
def var1(self):
return self._var1
b = B()
b.var1
b.var1 = 10 # 尝试给其赋值,会报错
- 抽象方法 @abstractmethod
实现抽象类的特性,含有@abstractmethod修饰的父类不能实例化,子类必须实现所有被@abstractmethod装饰的方法
from abc import ABC, abstractmethod
class F(ABC):
@abstractmethod
def info(self):
print("info")
@abstractmethod
def f(self):
print('f')
class S(F):
def info(self):
print('S - info')
f = F() # 报错:父类不能被实例化
s = S() # 报错,子类没有实现父类所有抽象方法
@wraps()语法糖
由于Python的装饰器是通过闭包实现的,所以在装饰函数中,获取导的属性并非原函数,这就不是很完美
def logger(fn):
def wrap_function(*args, **kwargs):
"""
这是装饰器函数
"""
print('装饰器中。。。。')
return fn(*args, **kwargs)
return wrap_function
@logger
def fnA():
"""
fnA中的打印
"""
print('正在运行fnA....')
print(fnA.__name__, fnA.__doc__)
# 输出:
'''
wrap_function
这是装饰器函数
'''
加上@wraps() 后,修饰内部函数,保证这个内部函数带有传进来这个函数的属性
from functools import wraps
def logger(fn):
@wraps(fn)
def wrap_function(*args, **kwargs):
"""
这是装饰器函数
"""
print('装饰器中。。。。')
return fn(*args, **kwargs)
return wrap_function
@logger
def fnA():
"""
fnA中的打印
"""
print('正在运行fnA....')
print(fnA.__name__, fnA.__doc__)
# 输出:
'''
fnA
fnA中的打印
'''
类装饰器
通过 __call__ 将类变成调用对象
class Logger:
def __init__(self, func):
self.func = func
def print_console(self):
print('console print')
def __call__(self, *args, **kwargs):
self.print_console()
self.func()
@Logger
def fnA():
print('fnA正在运行。。。')
fnA()
# 输出
'''
console print
fnA正在运行。。。
'''
带参数的类装饰器
class Logger:
def __init__(self, prefix):
self.prefix = prefix
def print_console(self):
print('console print', self.prefix)
def notify(self):
self.print_console()
def __call__(self, func):
def wrapper(*args, ** kwargs):
print(self.prefix, 'wrapper before...')
self.notify()
ret = func(*args, ** kwargs)
print(self.prefix, 'wrapper after...')
return ret
return wrapper
@Logger(prefix='Logger')
def fnA():
print('fnA正在运行。。。')
fnA()
# 输出
'''
Logger wrapper before...
console print Logger
fnA正在运行。。。
Logger wrapper after...
'''
有了类装饰器,就可以使用继承了
from functools import wraps
class Logger:
def __init__(self, prefix='logger'):
self.prefix = prefix
def print_console(self):
print('console print', self.prefix)
def notify(self):
self.print_console()
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print(self.prefix, 'wrapper before...')
self.notify()
ret = func(*args, **kwargs)
print(self.prefix, 'wrapper after...')
return ret
return wrapper
class EmailLogger(Logger):
def __init__(self, prefix='Email', email='abc@test.com'):
Logger.__init__(self, prefix)
self.email = email
def print_email(self):
print('email print:', self.email)
def notify(self):
self.print_email()
@EmailLogger(email='test@test.com')
def fnA():
print('fnA正在运行。。。')
fnA()
# 输出:
'''
Email wrapper before...
email print: test@test.com
fnA正在运行。。。
Email wrapper after...
'''