python lazy import

Python import实现

Python 提供了 import 语句来实现类库的引用,下面我们详细介绍当执行了 import 语句的时候,内部究竟做了些什么事情。

当我们执行一行  from package import module as mymodule 命令时,Python解释器会查找package这个包的module模块,并将该模块作为mymodule引入到当前的工作空间。所以import语句主要是做了二件事:

  1. 查找相应的module
  2. 加载module到local namespace

下面我们详细了解python是如何查找模块的。

查找module的过程

在import的第一个阶段,主要是完成了查找要引入模块的功能,这个查找的过程如下:

  1. 检查 sys.modules (保存了之前import的类库的缓存),如果module被找到,则⾛到第二步。
  2. 检查 sys.meta_path。meta_path 是一个 list,⾥面保存着一些 finder 对象,如果找到该module的话,就会返回一个finder对象。
  3. 检查⼀些隐式的finder对象,不同的python实现有不同的隐式finder,但是都会有 sys.path_hooks, sys.path_importer_cache 以及sys.path。
  4. 抛出 ImportError。

sys.modules

对于第一步中sys.modules,我们可以打开Python来实际的查看一下其内容:

可以看到sys.modules已经保存了一些包的信息,由这些信息,我们就可以直接知道要查找的包的位置等信息。

finder、loader和importer

在上文中,我们提到了sys.meta_path中保证了一些finder对象。在python中,不仅定义了finder的概念,还定义了loader和importor的概念。

  • finder的任务是决定自己是否根据名字找到相应的模块,在py2中,finder对象必须实现find_module()方法,在py3中必须要实现find_module()或者find_loader()方法。如果finder可以查找到模块,则会返回一个loader对象(在py3.4中,修改为返回一个module specs)。
  • loader则是负责加载模块,它必须实现一个load_module()的方法。
  • importer 则指一个对象,实现了finder和loader的方法。因为Python是duck type,只要实现了方法,就可以认为是该类。

sys.meta_path

在Python查找的时候,如果在sys.modules没有查找到,就会依次调用sys.meta_path中的finder对象。默认的情况下,sys.meta_path是一个空列表,并没有任何finder对象。

我们可以向sys.meta_path中添加一些定义的finder,来实现对Python加载模块的修改。比如下例,我们实现了一个会将每次加载包的信息打印出来的finder。

当我们执行的时候,就可以看到系统加载socket包时所发生的事情。

sys.path hook

Python import的hook分为二类,一类是上一章节已经描述的meta hook,另一类是 path hook。

当处理sys.path(或者package.path)时,就会调用对应的一部分的 Pack hook。Path Hook是通过向sys.path_hooks 中添加一个importer生成器来注册的。

sys.path_hooks 是由可被调用的对象组成,它会顺序的检查以决定他们是否可以处理给定的sys.path的一项。每个对象会使用sys.path项的路径来作为参数被调用。如果它不能处理该路径,就必须抛出ImportError,如果可以,则会返回一个importer对象。之后,不会再尝试其它的sys.path_hooks对象,即使前一个importer出错了。

详细可以参考registering-hooks

python import hooks

在介绍完Python的引用机制与一些实现方法后,接下来我们介绍一些关于如何根据自己的需求来扩展Python的引用机制。

在开始详细介绍前,给大家展示一个实用性不高,但是很有意思的例子:让Python在执行代码的时候自动安装缺失的类库。我们会实现一个autoinstall的模块,只要import了该模块,就可以打开该功能。如下所示,我们尝试引入tornado库的时候,iPython会提示我们没有安装。然后,我们引入了autoinstall,再尝试引入tornado,iPython就会自动的安装tornado库。

这个功能的实现其实很简单,利用了sys.meta_path。autoinstall的全部代码如下:

 

import hook的重要性

我们为什么需要Python import的hook呢?使用import的hook可以让我们做到很多事情,比如说当我们的Python包存储在一个非标准的文件中,或者Python程序存储在网络数据库中,或者像py2exe一样将Python程序打包成了一个文件,我们需要一种方法来正确的解析它们。

其次,我们希望在Python加载类库的时候,可以额外的做一些事情,比如上传审计信息,比如延迟加载,比如自动解决上例的依赖未安装的问题。

所以,import系统的Hook技术是值的花时间学习的。

如何实现import hooks

Python提供了一些方法,让我们可以在代码中动态的调用import。主要有如下几种:

  1. __import__ : Python的内置函数
  2. imputil : Python的import工具库,在py2.6被声明废弃,py3中彻底移除。
  3. imp : Python2 的一个import库,py3中移除
  4. importlib : Python3 中最新添加,backport到py2.7,但只有很小的子集(只有一个函数)。

Python2 所有关于import的库的列表参见Importing Modules。Python3 的可以参考Importing Modules PEP 0302 — New Import Hooks 提案详细的描述了importlib的目的、用法。

一些Hook示例

Lazy化库引入

使用Import Hook,我们可以达到Lazy Import的效果,当我们执行import的时候,实际上并没引入该库,只有真正的使用这个库的时候,才会将其引入到当前工作空间。 具体的代码可以参考github。 实现的效果如下:

它的实现也很简单:

posted @ 2018-06-13 11:27  Andy.gbhu  阅读(1262)  评论(0)    收藏  举报