之前我们介绍了如何利用WSGI
写一个简单的web
应用框架。写出来之后,项目虽然能用,如果还没有看过上述文章的,建议先看下,以便做到承上启下:
python|写一个简单的web应用框架: juejin.cn/post/722635…
本文的python
环境为:
在上述文章中,我们调用框架后,在定义路由的时候,使用的代码如下:
myWeb.Routes("/index",index) myWeb.Routes("/123",d345)
如上代码,虽然能够完成工作,但是不够美观。我们想要我们的框架像flask
那样,使用装饰器来定义路由,例如,这样:
routes(path="/123",methods="post") def helloWold(r): return (200,"hello world")
这样的话,就不用再额外使用Routes
将路由和函数绑定了。
类装饰器应该怎么写?
要实现上述功能,需要用到类装饰器,这里需要先简单阐述一下如何使用类装饰器,我们之前使用的都是函数来做的装饰器。
再探类私有方法
类装饰器需要重写__init__
方法和__call__
方法。如之前文章所述,前者在实例化类为对象的时候进行调用,而后则则是在调用对象的时候才会调用。
这里写一个简单的案例,来复习一下__init__
和__call__
方法。
上述代码,就写了一个类testClass
,在该类中,我们定义了__init__
方法和__call__
方法,我们为每隔一方法打印了一条输出语句。
最后我们定义了一个类testClass
实例test1
。而后再运行该实例。
运行后效果如下:
通过输出,我们可以发现,在将类实例化为对象的时候,就启动了__init__
方法,而__call__
方法,则需要调用实例的时候,才能运行。
装饰器调用方法
我们目前看到的装饰器调用方法,都是在函数前面定义@
而后跟上装饰器名称,如果有参数的话,会跟上参数,但是这都是语法糖的效果。
看一个装饰器例子:
上述代码,我们定义了一个装饰器decorator
,该装饰器仅是在调用函数前后打印输出一些内容。而后再test3
函数上面,使用@
来调用装饰器。最后执行函数test3
。
代码执行效果如下:
上述代码中,所谓的语法糖就是@
后面的语句,下面我们将不是用语法糖,来实现一下这个操作。
该代码,我们不使用python
语法糖,所以我们定义手动将函数test3
作为参数,传入decorator
装饰器中,装饰器返回wrapper
函数,我们将使用f
来接收,最后我们通过调用f
函数来实现装饰器的调用。
代码执行结果如下:
还有一种情况是,如果装饰器带参数了,这个时候需要在装饰器外部在定义一层函数,用于接收参数,例如代码如下:
上述代码,使用了re
来接收装饰器本身的参数,其他的和上述一致,我们执行代码,结果如下:
这里之所以强调装饰器,是因为我们想写函数式的装饰器来接收参数。
类作为装饰器
类善用私有方法,也可以实现装饰器的效果,例如:
上述代码,我们使用类的私有方法,来定义装饰器,上述代码如果不是语法糖的话,那么下面这段代码,应该如何调用呢?
@decorator def test3(): print("哈哈哈")
如果不是语法糖的话,是不是应该是
f = decorator(test3) f()
所以,需要使用__init__
方法来接收函数,而在call
方法中,我们直接写代码即可,中间需要调用从__init__
传过去的方法。
代码执行后,结果如下:
如果装饰器需要传参,传参这个动作就是类+()
,就会执行底层方法__call__
,所以,参数需要在__init__
中接收,而函数需要在__call__
中去接收,代码如下:
这个代码写的相对复杂,这里解析一下,我们将上述装饰器拆分为普通函数,代码如下:
f = decorator(x=3,y=5) f1 = f(test3) print(f1(3,5))
好了,现在看起来是不是感觉好多了呢?我们调用decorator(x=3,y=5)
,该代码是将类初始化为对象,所以需要在__init__
方法中去解析传过来的x
和y
,而f(test3)
,则是对象的调用了,需要需要去__call__
找到传入的函数。这个时候__call__
里面又是一个闭包函数,将闭包函数返回后,得到f1
,最后我们执行f1
函数,就先当于执行了wraper()
函数,如果需要传参的话,我们直接写入参数即可,因为在闭包函数wrapper
中,我们也传参了的: *args,**kwargs
。
所以上述代码,使用@
语法糖装饰器的话,执行结果如下:
如何注册路由
有了上面类装饰器的铺垫,再来写解析路由的话,就非常容易了,我们首先要确定,装饰器中应该传入什么样的值?作为绑定路由信息来说,我们最需要知道的是路由值和请求方法,所以我们想定义绑定函数类似于如下:
routes(path="/123",methods="post") def helloWold(r): return (200,"hello world")
那作为routes
装饰器,我们需要在其__init__
方法中接收path
和methods
的值,在__call__
方法中获取函数值。
当注册路由之后,我们直接将其存储到字典中,整个注册路由就写完了。
我们可以查看代码:
如上代码,我们先定义了2个字典:mapGetRoute
和mapPostRoute
来存储GET
请求和POST
请求的信息。
当进行函数注册的时候,需要获取装饰器传上来的path
和methods
,而后根据其请求方法,将数据存储到对应的字典中,若是指定的ALL
,则2个都存储。
最后只需要在启动函数application
中,对客户端请求进行解析,如果存在注册路由的函数,就直接获取存储的函数值调用就完事了。
上述代码,我们会对每一个请求进行判断,而后取出相应字典中的函数,并且执行后,将结果返回回去。注意,这里还是有问题的,若请求没有在路由字典中,则func
的值为None
,就会抛错,我们可以定义一个万金油路由,若匹配不到,就重定向到该路由函数中去,向客户端返回404
即可。
改善后的框架代码,我也放到了gitee
上: gitee.com/pdudo/golea…
启动wsgi
应用,我们直接使用的wsgiref
,代码案例如下:
总结
这篇文章,我们介绍了类作为装饰器应该如何调用,最后我们通过该特性,复写了一下我们框架的路由解析,让其路由函数和其他普通函数分开好看一点 。类写装饰器,其实是善用了其私有函数__init__
和__call__
。这里要注意一下,装饰器带参数和不带参数对应的类装饰器,都是不同的。