学习了函数实现的 Python 装饰器后,关于装饰器的内容还没完。Python 装饰器还是属于元编程的范畴,一谈到元(Meta), 元编程,往往能用简单的方式实现比较神奇的效果 -- 小渣男的非死不可除外。Python 还允许用类来实现装饰器,原理上就是能让 Python 对象函数用,见之前的一篇 Python 对象当函数使用及动态添加方法。关键就是类实现 __call__
函数,对象就变成 callable
, 与函数的装饰器实现归纳起来就是:一个 Python 类型能不能用 @ 当作装饰器来用只需看它是否是 callable
。
1 2 3 4 5 6 7 |
class Duck: def __call__(self): print('quack') duck = Duck() print(callable(duck)) # True duck() |
而且因为有了类,带属性的装饰器也会更简单,装饰器的属性就是构造函数的参数。还是来看怎么用类重新实现前面的 my_decorator
装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"before calling {func.__name__}") func(*args, **kwargs) print(f"after calling {func.__name__}") return wrapper @my_decorator def say_hello(firstname, lastname, **kwargs): print(f"Hello {firstname} {lastname}!", kwargs) say_hello("Steve", "Jobs", company="Apple", country="USA") |
用类实现装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class MyDecorator: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print(f"before calling {self.func.__name__}") self.func(*args, **kwargs) print(f"after calling {self.func.__name__}") @MyDecorator # my_decorator = MyDecorator(say_hello) def say_hello(firstname, lastname, **kwargs): print(f"Hello {firstname} {lastname}!", kwargs) say_hello("Steve", "Jobs", company="Apple", country="USA") |
执行后输出是一样的
before calling say_hello
Hello Steve Jobs! {'company': 'Apple', 'country': 'USA'}
after calling say_hello
声明 @MyDecorator 的时候会调用 __init__(self, func)
函数,并注册当前函数, 返回 MyDecorator 的一个对象。当调用 say_hello
时调用对象 my_decorator
的 __call__(self, *args, **kwargs)
函数。
带属性的类实现的装饰器
继续往前,想要创建一个 MyDecorator(101)
那样带属性装饰器,我的本能反应是在构造函数中做文章,可能是这样
1 2 3 |
def __init__(self, func, size): self.func = func self.size = size |
然而现在写成 @MyDecorator(101)
时就会有错了
TypeError: __init__() missing 1 required positional argument: 'size'
此路不通。正确的做法是要把 func
的传入移到 __call__()
函数中去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from functools import wraps class MyDecorator: def __init__(self, size): self.size = size def __call__(self, func): @wraps(func) def decorator(*args, **kwargs): print(f"{self.size}") print(f"before calling {func.__name__}") func(*args, **kwargs) print(f"after calling {func.__name__}") return decorator @MyDecorator(101) # my_decorator = MyDecorator(101) def say_hello(firstname, lastname, **kwargs): print(f"Hello {firstname} {lastname}!", kwargs) say_hello("Steve", "Jobs", company="Apple", country="USA") |
执行输出
101
before calling say_hello
Hello Steve Jobs! {'company': 'Apple', 'country': 'USA'}
after calling say_hello
这种加个属性就要把 func
从 __init__()
转移到 __call__()
中的变化,有些不好理解。
我们其实可以统一写法,不管装饰器带不带属性都能采用第二种写法,构造函数中放属性,__call__()
中放被装饰的函数,就是在其中要返回一个函数。
注:我们用 @wraps(func)
加注,并不会改变函数的执行行为,只是用 help(say_hello)
看到的是
Help on function say_hello in module __main__:
say_hello(firstname, lastname, **kwargs)
而不会是
Help on function decorator in module __main__:
decorator(*args, **kwargs)
更强类实现的 Decorator
由于类实现的 Decorator 运行期存在一个装饰器实例,可以保存状态数据,所以比单纯函数实现的装饰器更强大,我们从一个注册回调函数的例子开始。摘自网上的一个实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class FunctionManager: def __init__(self): print("初始化") self.functions = [] def execute_all(self): for func in self.functions: func() def register(self, func): self.functions.append(func) fm = FunctionManager() @fm.register def t1(): print("t1") @fm.register def t2(): print("t2") @fm.register def t3(): print("t3") fm.execute_all() |
执行结果是
初始化
t1
t2
t3
用装饰器实现一个简陋的 REST API
现在是不是感觉和 Flask 或 FastAPI 用装饰器声明 endpoint 的实现很接近了啊,在 Flask 中我们可以这么写
1 2 3 4 5 6 7 8 9 |
app = Flask(__name__) @app.get('/') def index(): return "hello world!" @app.get('/users') def list_users(): return "user list" |
我们自己来实现一个极度简陋的 REST API Chilli
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
from http.server import HTTPServer, BaseHTTPRequestHandler from urllib import parse class Chilli: def __init__(self): self.routes = {} def get(self, path): def route_decorator(func): self.routes[path] = func return route_decorator def run(self): server = HTTPServer(('localhost', 8080), MyHttpHandler) print('Starting server at 8080, use <Ctrl-C> to stop') server.serve_forever() class MyHttpHandler(BaseHTTPRequestHandler): def do_GET(self) -> None: parsed_path = parse.urlparse(self.path).path self.send_response(200) self.send_header('Content-Type', 'text/plain; charset=utf-8') self.end_headers() func = app.routes[parsed_path] self.wfile.write(f'{func()}\r\n'.encode('utf-8')) app = Chilli() @app.get('/') def index(): return 'hello world!' @app.get('/users') def users(): return 'user list' if __name__ == '__main__': app.run() |
运行后,访问 / 或 /users 的效果图如下
这里只实现了 GET, 也没有参数处理,但基本上用装饰器声明 endpoint 时有一点点模样了。
装饰器修饰类
装饰器除了修饰函数外,可以修饰类,这时候捕获的就不是 func,而是 cls
1 2 3 4 5 6 7 8 |
def class_info(): def wrapper(cls): print(f'class name {cls.__name__}') return wrapper @class_info() # Test = class_info(Test) class Test: pass |
执行输出为
class name Test
总结
最后,记住一点,能不能加上 @ 当作装饰器来用,满足的条件只要它是一个 callable 的类型,函数或实现了 __call__()
的对象。
无属性的装饰器
当装饰器为 class 时
1 2 3 4 5 |
@MyDecorator # 调用 __init__(self, func),返回它的对象, 假设是 my_decorator def say_hello(name): print(f'Hello {name}') say_hello('world') # 会调用 my_decorator.__call__(self) 函数 |
Python 解释到 @MyDecorator 行时会执行类的 __init__(self, func)
初始化函数,也就是会把当前函数名作为参数。然后在实际调用 say_hello()
函数时会调用 __call__()
函数
最终调用 say_hello('world') 的效果是
1 |
MyDecorator(say_hello)('world') |
当装饰器为函数时
1 2 3 4 5 |
@my_decorator # 调用 my_decorator(func) 函数, 假设是 wrapper def say_hello(name): print(f'Hello {name}') say_hello('world') # 会调用 my_decorator(func) 返回的函数 wrapper() |
Python 解释到 @my_decorator 行时会调用 my_decorator(func)
函数,并要求它的返回值是 callable 类型,即还是函数(或实现了 __call__()
函数的对象)。也是在实际调用 say_hello()
函数时才会调用 my_decorator(func)
返回的函数(或实现了 __call__() 的对象)
最终调用 say_hello('world') 的效果是
1 |
my_decorator(say_hello)('world') |
留意到,我们都是在实际调用 say_hello()
函数时才会去调用 @MyDecorator 或 @my_decorator 返回的 callable 对象(函数或实现了 __call__()
函数的对象)。如果我们只用装饰器来注册信息而不真的去调用被装饰的函数,让装饰器返回 callable 对象也就不是必须的。
带属性的装饰器
当装饰器为 class 时
1 2 3 4 5 |
@MyDecorator(size=101) # 调用类 MyDecorator 的 __init__self, size) 函数,返回它的对象, 假设对象为 my_decorator def say_hello(name): pass say_hello('world') # 会调用 my_decorator.__call__(self, func) 返回的函数 wrapper() |
Python 解释 @MyDecorator(size=101) 时只管装饰器的属性,而不管当前被修饰的函数,待到调用 say_hello('world')
时才有 func 的信息
最终调用 say_hello('world') 的效果是
1 |
my_decorator(size=101)(say_hello)('world') |
当装饰器为函数时
1 2 3 4 5 |
@my_decorator(size=101) # 调用 my_decorator(size) 函数,得到一个返回值又是函数的函数,假设是 wrapper def say_hello(name): pass say_hello('world') # 会调用 wrapper(func)('world') |
最终调用 say_hello('world') 的效果是
1 |
my_decorator(size=101)(say_hello)('world') |
学习 Python 的装饰到现在,由最开始对 @ 符号感到既熟悉又陌生,难免会把它与 Java 的注释联系起来,其实毫无关系。随着对它的特性及实现的深入,如带属性的装饰器,被装饰的函数有参数,或有返回值的情况; 它还能装饰类,仿佛不断的越陷越深,对它的理解愈加混乱。最后把思维又往回拉,重新回到 Python 装饰器的初心上来,一切又变得豁然开朗起来。说到底 Python 的装饰器就是对函数的包装再调用,罗列上面的调用效果到一块来
MyDecorator(say_hello)('world')
my_decorator(say_hello)('world')
MyDecorator(size=101)(say_hello)('world')
my_decorator(size=101)(say_hello)('world')
上面用 MyDecorator 和 my_decorator 分别表示类和函数,my_decorator 对应的是 MyDecorator 构造函数,没有别的区别。明白了装饰器的整个调用过程,实现上哪一步是否要返回一个 callable 对象就变得一目了然了。如此理解起来,Python 的装饰器比 Java 的注解变得简单的多,Java 的注解本身不代表什么,复杂的是后面的处理器。
链接:
- Python Class Based Decorator with parameters that can decorate a method or a function
- Python decorator实现的函数注册和类decorator,python,装饰,器
- Python 元编程
本文链接 https://yanbin.blog/python-class-implemented-decorator/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。