12.5 模块导入的特性

12.5.1 载入时执行模块

加载模块会导致这个模块被“执行”。也就是被导入模块的顶层代码将直接被执行。这通常包括设定全局变量以及类和函数的声明。如果有检查name的操作,那么它也会被执行。

当然,这样的执行可能不是我们想要的结果。你应该把尽可能多的代码封装到函数。明确地说,只把函数和模块定义放入模块的顶层是良好的模块编程习惯。

更多信息请参阅第14.1.1节以及相应的“核心笔记”。

Python加入的一个新特性允许你把一个已经安装的模块作为脚本执行。(当然,执行你自己的脚本很简单[$ foo.py],但执行一个标准库或是第三方包中的模块需要一定的技巧。)你可以在第14. 4. 3一节中了解更多。

12.5.2 导入(import)和加载(load)

一个模块只被加载一次,无论它被导入多少次。这可以阻止多重导入时代码被多次执行。例如你的模块导入了sys模块,而你要导入的其他5个模块也导入了它,那么每次都加载sys(或是其他模块)不是明智之举!所以,加载只在第一次导入时发生。

12.5.3 导入到当前名称空间的名称

调用from-import可以把名字导入当前的名称空间里去,这意味着你不需要使用属性/句点属性标识来访问模块的标识符。例如,你需要访问模块module中的var名字是这样被导入的:

12.5 模块导入的特性 - 图1

我们使用单个的var就可以访问它自身。把var导入到名称空间后就再没必要引用模块了。当然,你也可以把指定模块的所有名称导入到当前名称空间里:

12.5 模块导入的特性 - 图2

12.5 模块导入的特性 - 图3核心风格:限制使用“from module import*”

在实践中,我们认为“from module import*”不是良好的编程风格,因为它“污染”当前名称空间,而且很可能覆盖当前名称空间中现有的名字;但如果某个模块有很多要经常访问的变量或者模块的名字很长,这也不失为一个方便的好办法。

我们只在两种场合下建议使用这样的方法,一个场合是:目标模块中的属性非常多,反复键入模块名很不方便,例如Tkinter (Python/Tk)和NumPy (Numeric Python)模块,可能还有socket模块。另一个场合是在交互解释器下,因为这样可以减少输入次数。

12.5.4 被导入到导入者作用域的名字

只从模块导入名字的另一个副作用是那些名字会成为局部名称空间的一部分。这可能导致覆盖一个已经存在的具有相同名字的对象。而且对这些变量的改变只影响它的局部拷贝而不是所导入模块的原始名称空间。也就是说,绑定只是局部的而不是整个名称空间。

这里我们提供了两个模块的代码:一个导入者,impter.py和一个被导入者,imptee. py. impter. py使用from-import语句只创建了局部绑定。

12.5 模块导入的特性 - 图4

12.5 模块导入的特性 - 图5

运行这个导入者程序,我们发现从被导入者的观点看,它的foo变量没有改变,即使我们在importer.py里修改了它。

12.5 模块导入的特性 - 图6

唯一的解决办法是使用import和完整的标识符名称(句点属性标识)。

12.5 模块导入的特性 - 图7

完成相应修改后,结果如我们所料:

12.5 模块导入的特性 - 图8

12.5.5 关于future

回首Python 2. 0,我们认识到了由于改进、新特性和当前特性增强,某些变化会影响到当前功能。所以为了让Python程序员为新事物做好准备,Python实现了future指令。

使用from-import语句“导入”新特性,用户可以尝试一下新特性或特性变化,以便在特性固定下来的时候修改程序。它的语法是:

12.5 模块导入的特性 - 图9

只importfuture不会有任何变化,所以这是被禁止的(事实上这是允许的,但它不会如你所想的那样启用所有特性)。你必须显示地导入指定特性。你可以在PEP 236找到更多关于future的资料。

12.5.6 警告框架

future指令类似,有必要去警告用户不要使用一个即将改变或不支持的操作,这样他们会在新功能正式发布前采取必要措施。这个特性是很值得讨论的,我们这里分步讲解一下。

首先是应用程序(员)接口(Application programmers’ interface, API) 。程序员应该有从Python程序(通过调用warnings模块)或是C中(通过PyErr_Warn()调用)发布警告的能力。

这个框架的另个部分是一些警告异常类的集合。Warning直接从Exception继承,作为所有警告的基类,这些警告包括UserWarning, DeprecationWarning, SyntaxWarning和RuntimeWarning,都在第10章中有详细介绍。

另一个组件是警告过滤器,由于过滤有多种级别和严重性,所以警告的数量和类型应该是可控制的。警告过滤器不仅仅收集关于警告的信息(例如行号、警告原因等),而且还控制是否忽略警告,是否显示——可以是自定义的格式——或者转换为错误(生成一个异常)。

警告会有一个默认的输出显示到sys. stderr,不过有钩子可以改变这个行为,例如,当运行会引发警告的Python脚本时,可以记录它的输出记录到日志文件中,而不是直接显示给终端用户。Python还提供了一个可以操作警告过滤器的API。

最后,命令行也可以控制警告过滤器。你可以在启动Python解释器的时候使用-W选项。请参阅PEP 230的文档获得你的Python版本的对应开关选项。Python 2.1第一次引入警告框架。

12.5.7 从ZIP文件中导入模块

在2. 3版中,Python加入了从ZIP归档文件导入模块的功能。如果你的搜索路径中存在一个包含Python模块(.py、.pyc或。pyo文件)的。zip文件,导入时会把ZIP文件当作目录处理,在文件中搜索模块。

如果要导入的一个ZIP文件只包含。py文件,那么Python不会为其添加对应的。pyc文件,这意味着如果一个ZIP归档没有匹配的。pyc文件时,导入速度会相对慢一点。

同时你也可以为。zip文件加入特定的(子)目录,例如/tmp/yolk. zip/lib只会从yolk归档的lib/子目录下导入。虽然PEP 273指定了这个特性,但事实上使用了PEP 302提供的导入钩子来实现它。

12.5.8 “新的”导入钩子

导入ZIP归档文件这一特性其实新导入钩子(import hook, PEP 302)的“第一个顾客”。我们使用了“新”这个字,因为在这之前实现自定义导入器只能是使用一些很古老的模块,它们并不会简化创建导入器。另一个解决方法是覆盖import(),但这并不简单,你需要(重新)实现整个导入机制。

Python 2. 3引入的新导入钩子,从而简化了这个操作。你只需要编写可调用的import类,然后通过sys模块“注册”(或者叫“安装”)它。

你需要两个类:一个查找器和一个载入器。这些类的实例接受一个参数:模块或包的全名称。查找器实例负责查找你的模块,如果它找到,那么它将返回一个载入器对象。查找器可以接受一个路径用以查找子包(subpackages) 。载入器会把模块载入到内存。它负责完成创建一个Python模块所需要的一切操作,然后返回模块。

这些实例被加入到sys. path_hookso.sys.path_importer_cache只是用来保存这些实例,这样就只需要访问path_hooks—次。最后,sys. meta_path用来保存一列需要在查询sys. path之前访问的实例,这些是为那些已经知道位置而不需要查找的模块准备的。meta-path已经有了指定模块或包的载入器对象的读取器。