在Flask项目中,添加新的路由Flask提供了两种方式,一种是通过app.add_url_rule()
这个方法往Flask项目中插入路由。另外一种就是通过Flask提供的@route
装饰器给对应的方法加上装装饰器。第二种方法由于比较简单直观,是我在项目中最常用的注册路由的方式。虽然我一直在用,也知道怎么用,但是深层的思考这个装饰器是如何实现的我就不是很清楚。只知道这是Flask封装好的装饰器,直接拿来用就好了。
但是晚上睡觉的时候我不禁想起这种实现方式的优雅所在,想到以后我肯定会有很多场景使用到这个技巧,不如索性把它搞明白。因为不管你解决不解决,问题总是在哪里,现在不遇到以后总会再碰面。
Flask源码分析
def route(self, rule, **options): def decorator(f): endpoint = options.pop("endpoint", None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator
通过Flask源码可以看出route
装饰器的本质实现也是通过app.add_url_rule()
这个方法向项目中注册路由的,这个装饰器的实现相对简单。但是有项目经验的同学一定知道,我们在Flask项目中组织路由的话一般不直接在Flask对象下注册,一般我们会通过Flask提供的Blueprint(蓝图)对象,将视图函数划分成不同的蓝图然后将蓝图注册到Flask对象中。这样的方式能够方便的管理视图函数的层级,同时使视图函数的拔插更加灵活,这也是Flask的魅力所在。
对于Blueprint对象的话,同样也支持装饰器和调用函数两种方式注册路由。但是蓝图注册路由和Flask注册路由又有一定的区别,虽然本质上来看都是调用Flask的add_url_rule()
方法,但是蓝图对象的实现就有点巧妙,这种优雅的代码组织方式也是我学习的重点。
Blueprint对象是如何实现向Flask项目注册路由函数的呢?所有的答案都在Flask的源码当中。鉴于Flask的实现源码过于冗长(因为做了很多容错处理),我这边就不贴代码了,有兴趣的可以直接去项目源码中看。下面根据我对源码的理解,简单介绍一下蓝图是如何向Flask中注册路由的。
对于没有插入Flask对象中的蓝图对象,这两个对象是没有任何关联的,只有将蓝图注册到Flask对象中,这两个对象才会产生联系。所以视图的注册逻辑就是在蓝图的register方法中。
Blueprint对象实现了两个方法:add_url_rule()
和@route
装饰器,当我们调用这两个方式向蓝图中插入视图的时候,Blueprint并不会直接将视图函数注册到Flask当中。因为这两个对象并没有直接的联系,Blueprint这时候将视图函数的数据,参数等信息保存到了对象下的deferred_functions
属性当中了,这是一个数组类型属性。在向Flask对象中插入蓝图的时候会执行蓝图的register方法,这个方法会遍历deferred_functions
数组,然后调用app.add_url_rule()
完成视图函数的插入,归根结底都是使用了app.add_url_rule()
方法,不得不佩服Flask代码的巧妙啊。
对于Blueprint的延申-Redprint
对于体量比较大的Flask项目,似乎蓝图级别的划分也略显庞大。为了实现更为细致的路由管理,七月老师的Flask进阶教程当中介绍到,可以通过自己扩展实现比蓝图级别更为细致的视图管理对象。因为这个对象的编写逻辑是完全仿照蓝图对象的,所以七月老师把这个自己扩展的对象叫做红图。和蓝图对象一样,蓝图对象是插入到Flask对象中,那么红图就是插入到蓝图对象中。一层套一层,就是传说中的套娃。
一般的项目有这两个级别的视图管理已经绰绰有余了。Flask就是这样很多东西只是提供你底层方法,你需要自己扩展出适合你业务逻辑的代码。这是Flask微服务框架的核心,同样也是Flask灵活性的体现。对于这种特性我是十分喜欢的,项目中没有多余一行的冗余代码,强迫症的福音。下面给出Redprint的具体实现,逻辑就是上面的那张图,这个懂了可以帮助我们很好的理解Flask的路由注册机制。
class Redprint(): def __init__(self, name): self.name = name self.mound = [] def route(self, rule, **options): def decorator(f): if not rule.startswith("/"): _rule = "/" + rule else: _rule = rule self.mound.append((f, _rule, options)) return f return decorator def register(self, blueprint, url_prefix=None): if url_prefix is None: url_prefix = "/" + self.name for f, rule, options in self.mound: endpoint = self.name + "+" + options.pop("endpoint", f.__name__) blueprint.add_url_rule(url_prefix + rule, endpoint, f, **options)
代码是完全能够使用的,使用方法同Blueprint。这里就不给具体使用的例子了,还是要把代码看懂了才是真正的收获,复制粘贴只是手段。
实现类似Flask的消息驱动装饰器
驱动我去看Flask源码的源动力是因为最近写了一个小项目,使用Flask框架对微信公众平台做二次开发。我想实现的功能就是通过向公众号发送指定的指令来实现一些特定的功能,比如说自动论坛签到,查询天气,监控树莓派温度等等一些操作。但是问题是用户发过来的是文本消息,简单实现就是文本匹配然后调用相应的方法。但是这样做会导致一个问题就是文本处理这个方法会变得巨长无比,这肯定不是最好的实现方式。然后我很快就想到了Flask的路由装饰器,如果能将方法和关键字做绑定,通过装饰器实现函数的调用那岂不是美哉?
理解了Flask的代码实现我可以很快的拓展出一个Message类,这个类和Flask的蓝图类很相似。实现了装饰器和消息事件的处理,无论从哪方面来说都比之前的代码优雅,可读性也大大增加。Python的装饰器是真香啊。
import time class Message: def __init__(self): self.rules = {} def route(self, rule, **options): def decorator(func): self.rules[rule] = [func, options] def wrapper(**kwargs): return func(**kwargs) return wrapper return decorator def rule_process(self, rule): if rule not in self.rules: return print("该规则没有被注册") func = self.rules[rule][0] options = self.rules[rule][1] func(**options) handler = Message() @handler.route("login") def login(): print("我是登录函数") @handler.route("exit", delay=3) def exit(delay): print("我是退出函数,我可以添加参数,我将在三秒钟之后退出") time.sleep(delay) print("我是退出函数,我可以添加参数") if __name__ == '__main__': while True: message = input("请输入要执行的指令, 输入ESC退出\n") if message == "esc": break handler.rule_process(message)
简单的模拟了一下消息驱动的场景,这就是典型装饰器的使用场景了。有很多类似的库都是通过装饰器的方式实现的,比如说Flask控制台命令用到的click库,实现方式大同小异。所以可见这种编程思想受众面还是十分广泛的。
总之Python的装饰器是十分重要的思想,你会用装饰器你的代码不一定pythonic,但是如果你想代码pythonic那么装饰器是一定要掌握的。不管是在实际应用中还是在技术面试中,装饰器都是不可避免的一个环节,所以一定要熟练使用,多看源码。
上面文字密密麻麻,感觉并不是什么有营养的文字,但是我的能力也只有这个水平了。人总是要慢慢进步,罗马也不是一天建成的。写博客不是为了有人关注,只是想鞭策自己永远不要放弃思考。有一起学习的伙伴只是锦上添花,也希望可以帮助到一些人。
理解 Python 装饰器看这一篇就够了:https://foofish.net/python-decorator.html
python装饰器与语法糖@:点击跳转原文
cher
博主网站用的是什么