Inside Flask - flask 扩展加载过程
flask 扩展(插件)通常是以 flask_<扩展名字>
为扩展的 python 包名,而使用时,可用 import flask.ext.<扩展名>
来导入扩展包。一般使用方法见 flask 扩展。在最新的 0.11.1 代码中,不建议使用 flask.ext
加载扩展,可通过 flask_xxx
的名字直接调用扩展,那样就不需要 flask
自带的扩展机制,但了解一下原来的扩展实现机制还是很有意思的。
flask 在处理这些扩展的代码在 flask/ext
和 flask/exthook.py
中。
在 ext 包的 __init__.py
中,定义了一个 setup
函数 ::
def setup():
from ..exthook import ExtensionImporter
importer = ExtensionImporter(['flask_%s', 'flaskext.%s'], __name__)
importer.install()
然后就执行 setup 函数,并删除 setup 函数。setup 函数创建一个 flask.exhook.ExtensionImporter
,这个对象是真正执行扩展包导入的地方。
ExtensionImporter使用到 python 本身的模块搜索机制。我们知道 sys.path
是 python 的模块搜索路径,使用 import 导入模块时从 sys.path
列表中逐个查找。而 sys.meta_path
从字面看是元路径,在搜索时优先于 sys.path
(或者默认的隐式模块加载器)。meta_path
里面定义其实不是路径,而是模块搜索类 finder 的列表。finder 包含 find_loader
或 find_module
方法,用于查找对应模块加载器 loader,最后调用 loader 提供的 load_module
方法加载模块(注册到 sys.modules
)。
ExtensionImporter 既是 finder 同时也是 loader。setup 函数在初始化ExtensionImporter 时,传入了一个扩展包名模式列表和当前模块的名字(flask.ext
)。 flask.ext
在这里是作为一个包装器模块(必须条件), importer 把扩展模块挂在这个模块的名字之下。['flask_%s', 'flaskext.%s']
则是搜索实际模块名时用的字符串模板,如 flask.ext.sqlalchemy
则会转换为真实模块名 flask_sqlalchemy
或 flaskext.sqlalchemy
,并尝试 import (在下文中分析)。
首先,setup 函数调用 importer 的 install 方法,让 importer 把自己注册到 sys.meta_path
中成为一个 finder ::
def install(self):
sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]
那么,当代码中调用 from flask.ext.sqlalchemy import xxx
时,ExtensionImporter 的 find_module
函数被调用,并返回自身作为 loader ::
def find_module(self, fullname, path=None):
if fullname.startswith(self.prefix) and \
fullname != 'flask.ext.ExtDeprecationWarning':
return self
然后,按照模块导入的流程,load_module
函数被调用。
def load_module(self, fullname):
if fullname in sys.modules:
return sys.modules[fullname]
...
fullname 是模块全名 flask.ext.sqlalchemy
。在加载模块时,先从 sys.modules
查看是否已加载,如果未有加载,那么将 fullname 去掉 flask.ext
前缀,并转换为真实的模块名 ::
# 去掉前缀
modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff]
...
# 尝试加载真实模块
for path in self.module_choices:
realname = path % modname
try:
__import__(realname)
...
# 更新 sys.modules
module = sys.modules[fullname] = sys.modules[realname]
...
return module
至此,整个扩展模块的加载过程成功完成。