什么是装饰器?
一切返回一个可调用对象的对象都可用作装饰器。
python 本着一切都是对象的宗旨。那么对象又有哪些类别呢?
答: 可调用对象(callable)和不可调用对象。
可调对象有:方法、函数、实现了__call__的对象以及python内部实现的一系列内构函数。
作用:非入侵式修改其它对象。
在不修改其它对象的代码块的前提下,对其功能进行影响。
都有哪些用法?
- 函数(方法)装饰器
- 类装饰器
- 不带参数装饰器
- 带参数装饰器
例子1 对function进行装饰
def descriptor(func):
def wrapper(*args, **kwargs):
print("参数列表:", args, kwargs)
return func(*args, **kwargs)
return wrapper
@descriptor
def simple1():
print("simple1")
if __name__ == "__main__":
print("simple1:", simple1)
print("simple1 name:", simple1.__name__)
simple1()
输出
simple1: <function descriptor.<locals>.wrapper at 0x000001FD154371C0>
simple1 name: wrapper
参数列表: () {}
simple1
于是乎,我们可以在实际的func
调前后可以做一些特别的操作,比如日志跟踪,状态修改…
但是,请等一下:被装饰的对象已经不是原来的对象,比如名字变了。那么要想不变怎么做?
from functools import wraps
def descriptor(func):
# 使用python 提供的wraps再装饰一下就好了
@wraps(func)
def wrapper(*args, **kwargs):
print("参数列表:", args, kwargs)
return func(*args, **kwargs)
return wrapper
@descriptor
def simple1():
print("simple1")
if __name__ == "__main__":
print("simple1:", simple1)
print("simple1 name:", simple1.__name__)
simple1()
输出:
simple1: <function simple1 at 0x0000011E488071C0>
simple1 name: simple1
参数列表: () {}
simple1
例子 2 function代参数装饰器
def descriptor1(*args, **kwargs):
def function(wrapped):
@wraps(wrapped)
def wrapper(*wargs, **wkwargs):
print("装饰的参数", args, kwargs)
print("function参数", wargs, wkwargs)
fn = wrapped(*wargs, **wkwargs)
# to do some things
return fn
return wrapper
return function
@descriptor1(1, 2, 3, a="ad", b="ac")
def test(*args, **kwargs):
pass
if __name__ == "__main__":
test("a", "b", j=23, k=54)
输出:
装饰的参数 (1, 2, 3) {'a': 'ad', 'b': 'ac'}
function参数 ('a', 'b') {'j': 23, 'k': 54}
宗旨没变:可调用对象可作为装饰器
@descriptor1(1, 2, 3, a="ad", b="ac")
在传参数后相当于调用一次函数(方法)返回一个可调用对象。
装饰器 装饰类
def descriptor2(*args, **kwargs):
def function(wrapped):
@wraps(wrapped)
def wrapper(*wargs, **wkwargs):
print("装饰的参数", args, kwargs)
print("初始化参数", wargs, wkwargs)
obj = wrapped(*wargs, **wkwargs)
# to do some things
setattr(obj, "test", "add a propter")
return obj
return wrapper
return function
@descriptor2("A", d_type="class", name="A")
class A:
pass
if __name__ == "__main__":
a = A()
print("a.test: ", a.test)
输出
装饰的参数 ('A',) {'d_type': 'class', 'name': 'A'}
初始化参数 () {}
a.test: add a propter
装饰器 装饰 对象method
def descriptor1(*args, **kwargs):
def function(wrapped):
@wraps(wrapped)
def wrapper(*wargs, **wkwargs):
print("装饰的参数", args, kwargs)
print("method参数", wargs, wkwargs)
fn = wrapped(*wargs, **wkwargs)
# to do some things
return fn
return wrapper
return function
class B:
@descriptor1("descriptor1参数")
def test(self, *args, **kwarg):
print("类B 的test参数 ", args, kwarg)
if __name__ == "__main__":
b = B()
b.test("b test arg")
输出:
装饰的参数 ('descriptor1参数',) {}
method参数 (<__main__.B object at 0x000002303E130CA0>, 'b test arg') {}
类B 的test参数 ('b test arg',) {}
b.test("b test arg")
本应该打印一个参数'b test arg'
但是 还打印了<__main__.B object at 0x000002303E130CA0>
, 这是因self
是作为对象的方法的第一参数传入的。同理带参数的方法的装饰器可以参考例2
类作为装饰器 装饰其它对象
class C:
def __init__(self, *args, **kwargs):
print("类C init 参数", args, kwargs)
def __call__(self, *args, **kwargs):
print("B call参数", args, kwargs)
@C
class D:
def __init__(self):
print("对象D")
输出:
类C init 参数 (<class '__main__.D'>,) {}
B call参数 () {}
本质上和function作装饰一样,只不过类生成的对象得可调用。
但值得注意的是,类装饰器可以带参数去装饰其它类对象。
class C:
def __init__(self, *args, **kwargs):
print("类C init 参数", args, kwargs)
def __call__(self, *args, **kwargs):
print("C call参数", args, kwargs)
return self
@C("C作为装饰器带参数了")
class D:
def __init__(self):
print("对象D")
if __name__ == "__main__":
d = D()
输出:
类C init 参数 ('C作为装饰器带参数了',) {}
C call参数 (<class '__main__.D'>,) {}
C call参数 () {}
但是这里不对!!!!
C类的call调用了两次,且D类的初始化没用执行(类没有生成对象),原因?类型在传参数后返了一对对象,对象要生成装饰器,第一次第用了__call__, 在作为可调用代码在D中执行又调了一次,这次没有传参数。所以可以这样写。
class C:
def __init__(self, *args, **kwargs):
print("类C init 参数", args, kwargs)
def __call__(self, wrapped):
print("C call参数", wrapped)
def fn(*args, **kwargs):
print("参数 fn", args, kwargs)
return wrapped(*args, **kwargs)
return fn
@C("C作为装饰器带参数了")
class D:
def __init__(self):
print("对象D")
if __name__ == "__main__":
d = D()
输出结果为:
类C init 参数 ('C作为装饰器带参数了',) {}
C call参数 <class '__main__.D'>
参数 fn () {}
对象D
这下结果就符合预期的了。
最后,类作为装饰也可以装饰其它的函数,是一样的。
最后再强调一下,一切返回结果为可调用对象的对象都可以作为装饰器。