Python装饰器的一点总结

本文最后更新于:8 个月前

学习python也有一段时间了,用了也有一段时间了,但是有些东西不用就老会忘,索性写一点总结,备忘。

简介


装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

个人听到比较有趣的一个用处是:已经交付的产品,如果想要打补丁,别人已经在你的基础上开发了,你如果想要更改函数名称已经不太现实了,那么这个时候可以为原来的函数创建一个装饰器用来补救。

分类及形式


python装饰器的4种类型:函数装饰函数、函数装饰类、类装饰函数、类装饰类

废话不多说,先上代码:

# ----------------------函数装饰函数-------------------------#
def wrapFun(func):
    def inner(a, b):
        print('function name:', func.__name__)
        r = func(a, b)
        return r
    return inner

@wrapFun
def myadd(a, b):
    return a + b

print(myadd(2, 3))
print(myadd.__name__)

输出结果为:

function name: myadd
5
inner

个人理解:

  • 首先myadd函数定义的时候开辟了一个内存空间,可以进行加法运算;

  • @是装饰器的关键字,上面的例子就是说myadd外层包裹一个wrapFun,将myadd作为参数传递给了wrapFun,叫做func,指向一开始的内存空间;

  • 同时在wrapFunc内部定义了一个函数inner,里边的执行过程包含了func那部分过程,同时将内部函数返回(并未调用),而此时myadd已经变成了inner,执行myadd(2, 3)等同于执行inner(2,3)。

  • 约等于:myadd = wrapFun(myadd) -> inner;此时myadd已经换了一个内存空间了,再调用就是inner的全过程了。

# ----------------------函数装饰类-------------------------#
def wrapClass(cls):
    def inner(a):
        print('class name:', cls.__name__)
        cls.foo()
        return cls(a)
    return inner


@wrapClass
class Foo():
    def __init__(self, a):
        self.a = a

    @classmethod
    def foo(cls):
        print('Do sth at this time')

    def fun(self):
        print('self.a =', self.a)


m = Foo('xiemanR')
print(Foo.__name__)
print(type(m))
m.fun()

输出结果:

class name: Foo
Do sth at this time
inner
<class '__main__.Foo'>
self.a = xiemanR

与上边的大同小异,这里就不再分析了……

# ----------------------类装饰函数-------------------------#
class ShowFunName():
    def __init__(self, func):
        self._func = func

    def __call__(self, a):
        print('function name:', self._func.__name__)
        return self._func(a)


@ShowFunName
def Bar(a):
    return a

print(Bar('xiemanR'))

输出结果:约等于 Bar = ShowFunName(Bar), Bar的内存空间传递给了Bar._func,然后默认执行__call__函数。

function name: Bar
xiemanR

最后一类:

# ----------------------类装饰类-------------------------#
class ShowClassName(object):
    def __init__(self, cls):
        self._cls = cls

    def __call__(self, a):
        print('class name:', self._cls.__name__)
        return self._cls(a)


@ShowClassName
class Foobar(object):
    def __init__(self, a):
        self.value = a

    def fun(self):
        print(self.value)

a = Foobar('xiemanR')
print(type(a))
a.fun()

输出结果:相当于Foobar = ShowClassName(Foobar),Foobar.cls 指向Foobar之前的内存空间,然后调用__call__函数返回了Foobar,Foobar绕了一圈,就是为了执行__call__返回Foobar之前的那部分代码。

class name: Foobar
<class '__main__.Foobar'>
xiemanR

其他用法

  • 装饰带不定长度参数的函数: 通常装饰器不只装饰一个函数,每个函数参数的个数也不相同,这个时候使用不定长参数*args,**kwargs
def clothes(func):
    def wear(*args, **kwargs):
        print('Buy clothes!{}'.format(func.__name__))
        return func(*args, **kwargs)
    return wear

@clothes
def body(part):
    print('The body feels could!{}'.format(part))

@clothes
def head(head_wear, num=2):
    print('The head need buy {} {}!'.format(num, head_wear))
body('hands')
head('headdress')

# 输出结果为:
Buy clothes!body
The body feels could!hands
Buy clothes!head
The head need buy 2 headdress!
  • 装饰器带参数
# 把装饰器再包装,实现了seasons传递装饰器参数。
def seasons(season_type):
    def clothes(func):
        def wear(*args, **kwargs):
            if season_type == 1:
                s = 'spring'
            elif season_type == 2:
                s = 'summer'
            elif season_type == 3:
                s = 'autumn'
            elif season_type == 4:
                s = 'winter'
            else:
                print('The args is error!')
                return func(*args, **kwargs)
            print('The season is {}!{}'.format(s, func.__name__))
            return func(*args, **kwargs)
        return wear
    return clothes

@seasons(2)
def children():
    print('i am children')

children()
print(children.__name__)

# 输出结果为:
The season is summer!children
i am children
wear

这个就稍微复杂一点,又需要再嵌套一层,我们先看seasons接收了装饰器传递进去的参数,是一个数字2,然后season返回了一个clothes函数,这个函数接收参数children,返回一个wear函数,此时children已经变成了wear函数了,wear最后执行的是最最开始children内存空间里的代码,打印了i am children。

整理一下思路:children -> clothese(2)(children) - > wear;最后执行wear()。

一个例子


语法糖博大精深,还需要努力学习,然后应用于实战。

举例说一下我之前写代码遇到的一个问题:我在一个主线程写了一个子线程,每隔一段时间从网络上爬取RSS的数据,结果呢有时候可能是因为网络原因卡死。那就设置一个超时的装饰器,等它超过时间没有返回结果就干掉重开。

线程卡死的地方大多数都是在io或者http请求那。

function_timeout这个模块便是处理这类问题的,废话不多说,上代码:

from func_timeout import func_set_timeout
import time
import func_timeout


@func_set_timeout(1) # 封装好的装饰器,只要task运行时间超过传进去的1秒,就会报出FunctionTimedOut的异常
def task():
    while True:
        print('hello world')
        time.sleep(1)


if __name__ == '__main__':
    try:
        task()
    except func_timeout.exceptions.FunctionTimedOut:
        print('task func_timeout')

总结


这类应用应该会有很多,发散思维多想想就会有收获的。

参考文章


[1] python装饰器的4种类型:函数装饰函数、函数装饰类、类装饰函数、类装饰类

[2] Python装饰器高级版—Python类内定义装饰器并传递self参数

[3] 定义一个带参数的装饰器



本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!