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