pyhton基础【31】装饰器
目录
概述
为什么需要装饰器
如何实现一个装饰器
带参数的装饰器
基于类的装饰器
带参数的类装饰器
总结
概述
装饰器是python语言中的语法糖,可以通过装饰器对函数的功能进行拓展。
为什么需要装饰器
我们假设你的程序实现了say_hello()和say_goodbye()两个函数。
def say_hello():print("hello!")def say_goodbye():print("hello!") # 此处应打印goodbyeif __name__ == '__main__':say_hello()say_goodbye()
假设上述代码中的say_goodbye函数出现了bug,为了之后能更好的维护,现在需要在调用方法前记录函数调用名称,以定位出错位置。
[DEBUG]: Enter say_hello()
Hello![DEBUG]: Enter say_goodbye()
Goodbye!
实现方式:
def say_hello():print("[DEBUG]: enter say_hello()")print("hello!") def say_goodbye():print("[DEBUG]: enter say_goodbye()")print("hello!")if __name__ == '__main__':say_hello()say_goodbye()
对上述代码进行优化:
def debug():import inspectcaller_name = inspect.stack()[1][3] # 返回函数名print("[DEBUG]: enter {}()".format(caller_name))def say_hello():debug()print("hello!")def say_goodbye():debug()print("goodbye!")if __name__ == '__main__':say_hello()say_goodbye()
上述代码需要在每个业务函数里都要调用一下debug()函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?
那么装饰器这时候应该登场了。
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是:为已经存在的函数或对象添加额外的功能。
如何实现一个装饰器
在早些时候(Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。
def debug(func):def wrapper():print("[DEBUG]: enter {}()".format(func.__name__))return func()return wrapperdef say_hello():print("hello!")say_hello = debug(say_hello)say_hello()
上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。
def debug(func):def wrapper():print("[DEBUG]: enter {}()".format(func.__name__))return func()return wrapper@debug
def say_hello():print("hello!")say_hello()
这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就无法正常使用。因为返回的函数并不能接收参数,你可以指定装饰器函数wrapper接收和原函数一样的参数:
def debug(func):def wrapper(something): # 指定一毛一样的参数print("[DEBUG]: enter {}()".format(func.__name__))return func(something)return wrapper # 返回包装过函数@debug
def say(something):print("hello {}!".format(something))say('小明')
这样你就解决了一个问题,但又多了若干个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数了。
def debug(func):def wrapper(*args, **kwargs):print("[DEBUG]: enter {}()".format(func.__name__))return func(*args, **kwargs)return wrapper@debug
def say(something):print("hello {}!".format(something))say('小明')
带参数的装饰器
假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。
def logging(level):def wrapper(func):def inner_wrapper(*args, **kwargs):print("[{level}]: enter function {func}()".format(level=level,func=func.__name__))return func(*args, **kwargs)return inner_wrapperreturn wrapper@logging(level='INFO')
def say(something):print("say {}!".format(something))# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say)@logging(level='DEBUG')
def do(something):print("do {}...".format(something))if __name__ == '__main__':say('hello')do("my work")
是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG'),它其实是一个函数,会马上被执行,只要它返回的结果是一个装饰器时,那就没问题。细细再体会一下。
基于类的装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象调用了__call__()方法,那么这个对象就是callable的。
class Test:def __call__(self):print('call me!')t = Test()
t() # call me
像__call__这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。运行这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个实例对象拥有了被调用的行为。回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数,然后运行__call__()并返回一个函数,也可以达到装饰器函数的效果。
class Logging:def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):print("[DEBUG]: enter function {func}()".format(func=self.func.__name__))return self.func(*args, **kwargs)@Logging
def say(something):print("say {}!".format(something))say('小明')
带参数的类装饰器
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接收的就不是一个函数,而是传入的参数。通过实例对象把这些参数保存起来。之后运行__call__方法并接收一个函数的引用,通过__call__方法内部的wrapper函数运行接收的函数。
class Logging:def __init__(self, level='INFO'):self.level = leveldef __call__(self, func): # 接收函数def wrapper(*args, **kwargs):print("[{level}]: enter function {func}()".format(level=self.level,func=func.__name__))func(*args, **kwargs)return wrapper # 返回函数@Logging(level='INFO')
def say(something):print("say {}!".format(something))say('小明')
总结
装饰器是Python的一个非常强大而又灵活的功能,它允许程序员在不修改原始函数或类代码的情况下,为函数或类增加新的功能。装饰器实际上是一个函数,它接收一个函数作为参数并返回一个新的函数。使用装饰器可以在代码执行过程中动态地增加功能,这使得装饰器在很多情况下非常有用。装饰器的作用有很多,包括但不限于以下几点:
- 增加函数功能
- 代码复用
- 权限校验
- 缓存与延迟计算
- 参数类型检查