Flask的插件还挺多,用过的都知道比如flask-sqlalchemy,安装的时候是使用pip install flask-sqlalchemy,使用的时候就成了from flask.ext.sqlalchemy import SQLAlchemy。使用的是flask.ext而不是flask_sqlalchemy。感觉还有点牛掰啊-_-,不过仅仅是看起来高大上,并没有什么卵用。在2016年4月13号正式被移出支持了,已经直接发出不建议使用的警告。本文还是来炒一下现饭,看看它背后的逻辑

比如flask-sqlalchemy原本是使用from flask_sqlalchemy import [anything]使用的,被变成了from flask.ext.sqlalchemy import [anything]。这是一种对于import的hook。我们知道要导入一个模块,不使用import声明也是可以的,可以使用__import__函数,返回得到的也是一个模块对象

import hook

下文简述,如果需要了解比较详细的资料可以查看后文给出的参考链接(python2和3的import机制是略有区别的)
import声明主要做了2件事情,查找模块,加载模块。我们说的hook发生在第一步查找模块。查找模块的步骤是

  1. 查找sys.modules缓存
  2. 在sys.meta_path中依次执行finder对象,找到就返回自身,所有的都没有找到则会报错
    注意:sys.path_hooks中的finder会被sys.meta_path调用执行(sys.meta_path和sys.path_hook都可以参与import hooks步骤)

精简版本实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys
class ExtensionImporter(object):
def install(self):
sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]
def find_module(self, fullname, path=None):
if fullname.startswith('flask.ext.'):
return self
def load_module(self, fullname):
if fullname in sys.modules:
return sys.modules[fullname]
modname = fullname.split('.')[-1]
realname = 'flask_' + modname
try:
__import__(realname)
except ImportError:
raise ImportError('No module named %s' % fullname)
module = sys.modules[fullname] = sys.modules[realname]
if '.' not in modname:
setattr(sys.modules['flask.ext'], modname, module)
return module

ExtensionImporter是一个finder对象,当执行import flask.ext时会把它加入到sys.meta_path列表,后续的import都会先执行它,判断满足要求后就会使用__import__导入然后加入到sys.modules,这样就实现了flask.ext.sqlalchemy导入

为什么会被废除呢。下面是我猜测的。首先本来这个东西没有实质的作用,就是看着炫酷而已。我们可以认为所有的扩展导入都会使用flask.ext.xxx,但是这对第三方扩展库是有要求的,那就是包名必须为flask_xxx才行,我猜测有的第三方库未必遵守这个规则(否则exthook.py中也不会出现['flask_%s', 'flaskext.%s'flaskext.另外一种形式),这就说明了有的作者使用了flaskextxxx这种包名。出现了第二种难免就会出现另外的。当使用者发现并非所有的第三方扩展都使用flask.ext导入的时候。社区也觉得没必要维持了,毕竟第一个写这个代码的人是不是因为刚刚了解了import机制,觉得很炫酷写上去的呢-_-,另外这种花哨的设计也可能对代码自动补全功能产生了负面影响。so,最后还是回到了原来的位置,直接使用import flask_sqlalchemy导入吧

参考

官方import hook文档
pycon2015 import演讲
flask-sqlalchemy