Decorator

简单装饰器

装饰器其实有点类似游戏中的Buff,红蓝Buff,红Buff可以减速、攻击附带,蓝Buff可以法术加成。

也可以类比于各种特效加成,比如仙剑四中武器的各种注灵加成,不同注灵方式可以加成不同的效果,比如冰冻、中毒、灼烧等等。 在python中,装饰器就是为了给原有函数加上额外的特效。

下面举例说明:

def test():
    print('just for test')

现在有个新的需求,要在执行函数后打印log,显示当前执行的函数是哪个,那可以这样:

def test():
    print('just for test')
    logging.info('test is running')

一个函数好办,要是有多个函数都要呢,每个函数都加的话就太冗余了。那么另外写个函数:

def logged(func):
    logging.info('{} is running'.format(func.__name__))
    func()

def test():
    print('just for test')

logged(test)

这样虽然可以,但是每次调用函数都要外加个壳子logged,显然是不合情理的,破坏了原有的代码逻辑。那怎么解决呢,这就是装饰器的出场时间了:

在这里,logged就是个装饰器,业务逻辑在test中实现,装饰器对test进行了装饰,也就是加了特效,专业术语叫AOP,即面向切面编程arrow-up-right

使用语法糖@, 在业务方法外层添加@logged即可

带参装饰器

由于装饰器本身也是一个函数,那么带参也是被允许的,那么如何实现呢?

可以看出来,在上面简单装饰器的基础上又加了一层封装,同时返回了一个装饰器decorator,Python解释器在解析到@logged(level='info')的时候,能够发现这层封装,并将参数level传入装饰器中。

保留原函数元信息

使用装饰器有个缺陷,就是会丢失原函数的元信息,比如函数的__name__、docstring、注解和参数签名等。

解决方法是使用另一个装饰器functools.wraps

类装饰器

内置装饰器

@staticmethod

静态函数staticmethod原本是可以在类外定义的,如:

但是呢,如果foo这个函数仅仅被类A内部函数调用,或者仅仅与类A相关的操作需要用到函数foo,那么放在外面就显得不太合适了,容易污染命名空间。此时既想把foo与类A关联起来,同时又想通过类名直接快捷访问,就可以通过@staticmethod来实现:

可以看出,staticmethod就是普普通通的函数,只不过刚好某个类需要用到它,就把它扔进去了,这个函数本身最好不去调用类的属性或成员函数。

@classmethod

@classmethod有个必要的首参,用于指向当前所在类,所以通常有类似这样的定义:

在上面的例子中,函数f1通过类对象cls定义并返回了一个A类的实例对象。

在爬虫框架Scrapy源码中,有个很常用的@classmethod, 就是crawl.py中的from_crawler函数:

该函数继承于Spider类中的from_crawler函数,最终返回的是一个spider实例对象。

@classmethod主要用于返回实例对象,同时可以直接访问类中属性及函数。

@staticmethod & @classmethod

@staticmethod@classmethod的对比随处可见。

先说共同点,两种装饰器对应的函数都可以通过以下两种方式调用:

  • ClassName.function()

  • Instance.function()

再看看不同点,从定义和调用方面区分:

  • @staticmethod对应函数通常只是与类有所关联,但与其对象属性关联不大或没有关联,要想访问类的方法和属性,需要通过类名或者self访问。

  • @classmethod第一个参数必须是自身类参数cls,名称随意,可以是self;而且因为持有类参数,所以可以通过cls直接访问类的方法和属性。

大部分情况下,添加这两个装饰器的目的就是为了方便通过类名直接访问,而无需实例化对象。

应用场景方面,

  • @staticmethod适用于普通函数,就是那种放不放类中都可以用,只不过恰好在这个类中而已。

  • @classmethod适用于创建实例对象,即通过这个函数返回一个类的实例对象

@property

至于属性装饰器@property就比较简单了,通常用于实现类内部某个属性的set,get操作,将被装饰的函数模拟成一个类的属性,然后可以对其进行直接读取和赋值。

参考资料

Last updated