Python 类实现的装饰器及简陋 REST API

学习了函数实现的 Python 装饰器后,关于装饰器的内容还没完。Python 装饰器还是属于元编程的范畴,一谈到元(Meta), 元编程,往往能用简单的方式实现比较神奇的效果 -- 小渣男的非死不可除外。Python 还允许用类来实现装饰器,原理上就是能让 Python 对象函数用,见之前的一篇 Python 对象当函数使用及动态添加方法。关键就是类实现 __call__ 函数,对象就变成 callable, 与函数的装饰器实现归纳起来就是:一个 Python 类型能不能用 @ 当作装饰器来用只需看它是否是 callable

而且因为有了类,带属性的装饰器也会更简单,装饰器的属性就是构造函数的参数。还是来看怎么用类重新实现前面的 my_decorator 装饰器

用类实现装饰器

执行后输出是一样的

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) 那样带属性装饰器,我的本能反应是在构造函数中做文章,可能是这样

然而现在写成 @MyDecorator(101) 时就会有错了

TypeError: __init__() missing 1 required positional argument: 'size'

此路不通。正确的做法是要把 func 的传入移到 __call__() 函数中去

执行输出

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 运行期存在一个装饰器实例,可以保存状态数据,所以比单纯函数实现的装饰器更强大,我们从一个注册回调函数的例子开始。摘自网上的一个实例

执行结果是

初始化
t1
t2
t3

用装饰器实现一个简陋的 REST API

现在是不是感觉和 Flask 或 FastAPI 用装饰器声明 endpoint 的实现很接近了啊,在 Flask 中我们可以这么写

我们自己来实现一个极度简陋的 REST API Chilli

运行后,访问 / 或 /users 的效果图如下

这里只实现了 GET, 也没有参数处理,但基本上用装饰器声明 endpoint 时有一点点模样了。

装饰器修饰类

装饰器除了修饰函数外,可以修饰类,这时候捕获的就不是 func,而是 cls

执行输出为

class name Test

总结

最后,记住一点,能不能加上 @ 当作装饰器来用,满足的条件只要它是一个 callable 的类型,函数或实现了 __call__() 的对象。

无属性的装饰器

当装饰器为 class 时

Python 解释到 @MyDecorator 行时会执行类的 __init__(self, func) 初始化函数,也就是会把当前函数名作为参数。然后在实际调用 say_hello() 函数时会调用 __call__() 函数

最终调用 say_hello('world') 的效果是

当装饰器为函数时

Python 解释到 @my_decorator 行时会调用 my_decorator(func) 函数,并要求它的返回值是 callable 类型,即还是函数(或实现了 __call__() 函数的对象)。也是在实际调用 say_hello() 函数时才会调用 my_decorator(func) 返回的函数(或实现了 __call__() 的对象)

最终调用 say_hello('world') 的效果是

留意到,我们都是在实际调用 say_hello() 函数时才会去调用 @MyDecorator 或 @my_decorator 返回的 callable 对象(函数或实现了 __call__() 函数的对象)。如果我们只用装饰器来注册信息而不真的去调用被装饰的函数,让装饰器返回 callable 对象也就不是必须的。

带属性的装饰器

当装饰器为 class 时

Python 解释 @MyDecorator(size=101) 时只管装饰器的属性,而不管当前被修饰的函数,待到调用 say_hello('world') 时才有 func 的信息

最终调用 say_hello('world') 的效果是

当装饰器为函数时

最终调用 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 的注解本身不代表什么,复杂的是后面的处理器。

链接:

  1. Python Class Based Decorator with parameters that can decorate a method or a function
  2. Python decorator实现的函数注册和类decorator,python,装饰,器
  3. Python 元编程

本文链接 https://yanbin.blog/python-class-implemented-decorator/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments