很多人学Flask,上来就用 @app.route('/'),觉得这东西就是个装饰器,挂上去就能用。真要问他请求进来后路由怎么找到对应函数的,他多半卡壳。这不是个例,我见过写了两年代码的同事,还在靠“感觉”工作。
路由说白了就是一张映射表。Flask内部维护了一个Map对象,里面存着URL和视图函数的绑定关系。你加一个路由,它就往表里插一条记录。请求进来,它就拿着请求的路径去这张表里匹配。匹配上了,调用对应的函数。匹配不上,返回404。
有人觉得这不就是字典查找吗。还真不是字典。Flask用的是Werkzeug的路由系统,底层是Radix树,也叫压缩前缀树。普通字典要找动态路径,比如 /user/<id> 这种,你得手写正则或者做字符串拆分。Radix树直接支持变量规则,它把路径按/分段,每段塞进树的节点里。查找的时候走树结构,比字典暴力匹配快得多,规则多了也稳定。
具体干活时,Flask在应用启动时会把所有 @app.route() 注册的规则全塞进 app.url_map 这个Map里。第一次请求进来,Flask检查 app.url_map 的值,解析出URL里哪些是固定段,哪些是变量。变量部分用尖括号包起来,比如 <int:id>,这告诉路由系统这个位置的值要转成整数。如果没指定类型,默认转字符串。
匹配过程里还有个小把戏。Flask会按规则的复杂度排序,静态路径排前面,动态路径排后面。这样访问 /login 这种固定页面不会被动态规则抢走。排序完了才走Radix树查找。
找完以后,Flask把匹配到的变量值塞进 request.view_args 这个字典里。你写的视图函数参数名跟路由变量名对不上,就会报TypeError。比如路由写了 <id>,函数参数写成 user_id,Flask找不到对应值,直接抛异常。
很多人被坑过的一个地方是蓝图的路由。蓝图本身没有独立的url_map,它注册到应用后,蓝图里的所有路由规则会合并到应用的同一个Map里。蓝图的名字会作为前缀拼到路径前面。比如蓝图叫 admin,蓝图里路由是 /dashboard,最终注册成 /admin/dashboard。这个合并过程在应用启动时就做完了。
还有种情况是路由写成了 /user/<id>/ 和 /user/<id> 两个版本。Flask默认会帮你处理斜线,但在某些复杂场景下会报重定向循环。原因就是路由系统匹配时,会把带尾斜线的规则当成目录,不带斜线的当成文件。两者并存时,匹配优先级会乱掉。
路由加太多也会出问题。Flask官方推荐不要超过1000个路由。Radix树在节点多起来后,查找效率虽然比线性扫描高,但构建树的时间和内存消耗会涨。真到了几千条路由,可以考虑分模块或改用其他框架的路由实现。
说白了,路由就是个路径解析和函数调用的桥梁。知道它怎么建表、怎么查找、怎么处理变量,你写代码时就不会对着报错瞎猜。下次看到 @app.route('/item/<id>'),你就知道它背后有个Radix树节点在等着匹配。函数参数名对不上时,也记得去查 request.view_args 里到底传了什么值。