1.装饰器功能
装饰器实质上是一个Python函数,主要功能是为已经原函数和对象添加额外的功能。
经常用于日志操作、用户登录、用户权限、数据读写操作前打开事务等之类需求。
能装饰函数和类,实现大量代码的重用,使代码更加简洁。、
2.装饰器使用
Python一切皆对象,函数也是一个function对象,所以能在函数中作为参数传递,例如
def info(func):
print("info")
return func
def login():
print("login")
f = info(login)
f()
这个就是装饰器的简单原理,login
函数作为参数传递到info
函数上,然后再info
的函数体调用login
函数的逻辑代码,运行结果依次打印
info
login
1)函数装饰器,不带参数
def info(func):
print("info")
return func
@info #1
def login():
print("login")
login()#2
该程序执行的效果跟上面的例子是一样的,@info
是装饰器的语法糖写法。#1行相当于执行了login= info(login)
,#2行相当于执行了login()
。
如果info
函数不返回func
,#2行login()
会报错TypeError: 'NoneType' object is not callable
,也就是说加入装饰器后login
是info
函数执行后的返回结果,也就是None
。
2)函数装饰器,带参数
def info(params):
def wrap(func):
print(params)
return func
return wrap
@info("hello") #1
def login():
print("login")
login()#2
输出的结果是:
hello
login
从上面的程序可以看出@info("hello")
相当于执行了
wrap = info("hello")
login = wrap(login)
然后login
得到wrap
函数返回的结果,然后执行原来login
函数的逻辑代码
login()
3)匹配带参数和不带参数的装饰器
def info(*args):
if len(args) == 1 and callable(args[0]): # 1 # hasattr(args[0], "__call__"):
print("不带参数", end=" ")
return args[0] # 2
else:
print("带参数, 参数为%d" % args, end=" ")
def wrap(func):
return func # 3
return wrap
@info
def login():
print("login")
login()
@info(100)
def logout():
print("logout")
logout()
输出的结果是:
不带参数 login
带参数, 参数为100 logout
#1行len(args) == 1 and callable(args[0])
检测args参数类型,我们知道@info
相当于执行login = info(login)
,函数login
作为参数传入info
函数里面,那么args
的长度为1,callable
则判断参数是调用的(函数或者实现__call__
的对象)。
因此可以看出login
函数的装饰器进入info
的if
逻辑,而logout
则进入else
逻辑。
4)函数装饰器,装饰类
def hello(self):
print("self = ", self)
print("hello!!!")
def info(cls):
if not hasattr(cls, "hello"):#2
setattr(cls, "hello", hello)#3
return cls
@info #1
class Login:
def __init__(self):
print("Login init")
login = Login()
login.hello()
输出结果是
Login init
self = <__main__.Login object at 0x000001E57EAE9640>
hello!!!
之前的例子是装饰在函数上,而这个例子则装饰在类上,从输出的结果看出来,@info
为Login
类额外添加hello
成员函数。
#2判断Login
是否已经定义了hello的函数,如果没有则通过#3行的setattr(object, name, value)
内置函数动态添加成员函数。
在Python源码中也常遇到这种用法,例如functools
库里面的total_ordering
函数,看下面的例子
import functools
@functools.total_ordering #1
class Login:
def __init__(self, num):
self.num = num
def __gt__(self, other):
return self.num - other.num > 0
a = Login(3)
b = Login(4)
print(a > b)
print(a >= b)#2
输出结果是
False
True
如果注释#1行的代码,#2行会报错
Traceback (most recent call last):
File "D:/PycharmProjects/NewTest/decoratorTest.py", line 123, in <module>
print(a <= b)#2
TypeError: '<=' not supported between instances of 'Login' and 'Login'
因此@functools.total_ordering
会为Login
类自动增加__le__
的函数。
下面分析一下total_ordering
的关键代码
_convert = { #1
'__lt__': [('__gt__', _gt_from_lt),
('__le__', _le_from_lt),
('__ge__', _ge_from_lt)],
'__le__': [('__ge__', _ge_from_le),
('__lt__', _lt_from_le),
('__gt__', _gt_from_le)],
'__gt__': [('__lt__', _lt_from_gt),
('__ge__', _ge_from_gt),
('__le__', _le_from_gt)],
'__ge__': [('__le__', _le_from_ge),
('__gt__', _gt_from_ge),
('__lt__', _lt_from_ge)]
}
def total_ordering(cls):
"""Class decorator that fills in missing ordering methods"""
# Find user-defined comparisons (not those inherited from object).
#判断装饰的对象cls是否存在_convert字典中的函数,而且是否是object继承的,返回一个列表
roots = {op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)}
if not roots:
raise ValueError('must define at least one ordering operation: < > <= >=')
root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
#等到一个字典的value值后,遍历value的元组,判断cls是否存在其他函数,以函数的名字作为判断
for opname, opfunc in _convert[root]:
if opname not in roots:
opfunc.__name__ = opname
setattr(cls, opname, opfunc)
#返回当前的类
return cls
从源码中结合注释可以看出能够为对象自动生成__lt__,__le__,__gt__,__ge__
等函数,从而时代码更加简洁。
5)对象装饰器,不带参数
前面讲的例子有一个共同点都是函数作为装饰器,下面使用对象作为装饰器。
class Info:
def __init__(self, func):
print("Info init")
self.func = func
def __call__(self, *args, **kwargs):
self.func(*args, **kwargs)
@Info #1
def login(*args, **kwargs):
print(args)
# login = Info(login)
print(type(login))#2
login("hello")
输出结果是
Login init
self = <__main__.Login object at 0x000001ADF86F9640>
hello!!!
从输出结果看出,#2行的打印login是一个类,因此#1行中@Info
相当于执行了login = Info(login)
,调用login("hello")
会执行__call__
函数逻辑。前面提到过callable
内置函数,需要实现__call__
才能调用(函数自带__call__
)。
5)对象装饰器,带参数
class Info:
def __init__(self, params):
print("Info init")
self.params = params
def __call__(self, func):
print("Info __call__")
def wrap(*args, **kwargs):
print("装饰器的参数为%s" % self.params)
func(self.params)
return wrap
@Info("hello") # 1
def login(*args, **kwargs):
print(args)
# info = Info("hello")
# login = info(login)
# print(type(login))
login() #2
输出结果是
Info init
Info __call__
装饰器的参数为hello
('hello',)
从输出结果看出#1行@Info("hello")
相当于执行了
info = Info("hello")
login = info(login)
从Info
类中传入login
作为参数并且执行,#2行login()
相当于调用了wrap
函数。
3.总结
从上面各个例子看出@info
是装饰器特别的写法,其实质上也是一个函数或者是类,执行的都是de = info()
,然后再以装饰的函数作为参数再次调用de(func)
。看看下面的例子:
@a
@b
@c
def d():
pass
相当于执行d = a(b(c(d)))
(假设都返回当前函数),一步一步进行调用。
从装饰器的使用到语法上看出,装饰器能大大重用代码,而且使代码更加简洁,但是相对影响了可读性,需要深入学习才能理解透彻。
Python装饰器的基本用法暂到此结束。