前言
在Python用import或者from...import来导入相应的模块。模块其实就是一些函数和类的集合文件,它能实现一些相应的功能,当我们需要使用这些功能的时候,直接把相应的模块导入到我们的程序中,我们就可以使用了。
Python中的Module是比较重要的概念。常见的情况是,事先写好一个.py文件,在另一个文件中需要import时,将事先写好的.py文件拷贝到当前目录,或者是在sys.path中增加事先写好的.py文件所在的目录,然后import。这样的做法,对于少数文件是可行的,但如果程序数目很多,层级很复杂,就很吃力了。有没有办法,像Java的Package一样,将多个.py文件组织起来,以便在外部统一调用,和在内部互相调用呢?答案是有的。
原理剖析
主要是用到Python的包的概念,python __init__.py在包里起一个比较重要的作用,要弄明白这个问题,首先要知道,python在执行import语句时,到底进行了什么操作,按照python的文档,它执行了如下操作:第1步,创建一个新的,空的module对象(它可能包含多个module);
第2步,把这个module对象插入sys.module中
第3步,装载module的代码(如果需要,首先必须编译)
第4步,执行新的module中对应的代码来创建其所定义的对象。
在执行第3步时,首先要找到module程序所在的位置,其原理为:如果需要导入的module的名字是m1,则解释器必须找到m1.py,它首先在当前目录查找,然后是在环境变量PYTHONPATH中查找。PYTHONPATH可以视为系统的PATH变量一类的东西,其中包含若干个目录。如果PYTHONPATH没有设定,或者找不到m1.py,则继续搜索与python的安装设置相关的默认路径,在Unix下,通常是/usr/local/lib/python。事实上,搜索的顺序是:当前路径(以及从当前目录指定的sys.path),然后是PYTHONPATH,然后是python的安装设置相关的默认路径。正因为存在这样的顺序,如果当前路径或PYTHONPATH中存在与标准module同样的module,则会覆盖标准module。也就是说,如果当前目录下存在xml.py,那么执行import xml时,导入的是当前目录下的module,而不是系统标准的xml。
其次,如果导入的文件并没有被编译成字节码,Python会将其进行解释,如果已经存在.pyc的文件,python会检查字节码的时间戳,如果比源码的时间戳旧,程序运行时便会重新编译成新的字节码,否则跳过编译过程。
在执行第四步是:程序会将导入的文件从头到尾执行一遍,在此过程中任何对变量名进行的赋值操作,都会产生得到的模块文件的属性,但是要注意这些对变量名的赋值操作必须是在模块文件的顶层进行的操作,例如使用def语句来定义函数,模块文件中便会添加这个定义的函数属性。
了解了这些,我们就可以先构建一个package,以普通module的方式导入,就可以直接访问此package中的各个module了。
案列
Python中的package定义很简单,其层次结构与程序所在目录的层次结构相同,这一点与Java类似,唯一不同的地方在于,Python中的package必须包含一个__init__.py的文件。例如,我们可以这样组织一个package:
package1/
__init__.py
subPack1/
__init__.py
module_11.py
module_12.py
module_13.py
subPack2/
__init__.py
module_21.py
module_22.py
……
__init__.py可以为空,只要它存在,就表明此目录应被作为一个package处理。当然,__init__.py中也可以设置相应的内容,下文详细介绍。
标准 Import
Python中所有加载到内存的模块都放在sys.modules。当import一个模块时首先会在这个 列表中查找是否已经加载了此模块,如果加 载了则只是将模块的名字参加 到正在调用import的模块的Local名字空间中。如果没有加载则从sys.path目录中遵守模块名称查找模块文件,模 块文件可以是py、pyc、pyd,找到后将模块载入内存,并参加 到sys.modules中,并将名称导入到当前的Local名字空间。
可以看出了,一个模块不会重复 载入 。多个不同的模块都可以用import引入同一个模块到自己的Local名字 空间,其实背后的PyModuleObject对象只有一个。
说一个容易漠视 的问题,import只能导入模块,不能导入模块中的对象(类、函数、变量等)。 如一个模块 A(A.py)中有个函数getName,另一个模块不能通过import A.getName将getName导入到本模块,只能用import A。如果想只导入特定的类、函数、变量则用from A import getName即可。
嵌套Import
嵌套import,我分两种情况 :
顺序嵌套:本模块导入A模块(import A),而A中又有import语句,会激活另一个import动作,如import B,而B模块又可以import其他模块,一直下去。
对这种嵌套对比 容易了解,注意一点就是各个模块的Local名字空间是独立的,所以上面的例子,本模块import A完了后本模块只能造访 模块A,不能造访 B及其他模块。虽然模块B已经加载到内存了,如果要造访 还要在明确 的在本模块中import B。
循环嵌套:【业务应用场景有限,暂不深究】
模块初始化
模块会在导入的时候自动执行可执行语句。结果都会保存在模块自己的名字空间之下,访问时,需要加前缀 name.脚本初加载时,python会顺序执行所有可以执行的东西。 如果是函数与类定义,它就顺序将定义放到全局表里。 这里全局变最也是一样。python从import语句开始执行,执行到这句话today 就被初始化了。 如果这个模块被其它模块import ,那个在import 的时候, today会被加载。 不过跨模块的全局变量,在python里有些古怪。要小心使用。
跨模块全局变量
昨天遇到一个诡异的问题
多个.py文件去操作访问一个全局变量的时候,有个py文件访问到的是空值,昨天折腾了一晚上没搞定,上午突然想到是否是调用了2次定义全局变量module导致
所以调试了下,发现:
1. 定义该全局变量的module确实被import了2次,而且是当做不同的sys.module的key
2. 于是第二次 import的时候当做另外一个全局变量了
这个问题的原因是:
1. python import 包的机制是,import进来的和默认的系统的module了,都放在sys.module这个字典里面
2. 多个py文件再次import的时候,会先去sys.module里面检查是否已经import了,如果已经import了,就不再重复import,否则就import进来
3. 问题的关键是,如果a.py 定义如下:
import abc
如果直接从a目录执行,sys.module里面有个key叫abc
b.py 定义如下import abc
如果 b.py被调用的地方是采用包结构,比如.
from ./../**/ import b
这个时候, sys.module里面的key则是 ../../**/b
这样,就重新加载了
解决该问题的办法是,尽量都用包结构去import,这样能保证import只有一次