Python装饰器的一点总结
本文最后更新于:2 年前
学习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] 定义一个带参数的装饰器