模块化是现代程序语言的一大特点,尤其是随着软件规模越来越大,按功能分块开发,便于多个开发人员相互协作,也便于后期维护。

Python通过import机制将各模块组织起来。

模块的形式:

模块是一个以.py结尾的文件,可以定义变量、方法与类;同时,在模块内可以import进其他模块,如果需要引用其他模块内的变量、方法或对象。

import的本质:

import某一模块,相当于在当前环境中把某块内的代码执行一遍,使得该模块定义的变量、方法可见。为防止名称冲突,引用时通过模块名.变量名(module.variable)的方式访问。如果在当前环境中还有同样名字的变量variable,它们俩之间没有任何关系,引用时要区分开。

例一:


[python]  view plain copy



1. #t1.py  
2.   
3. import t2  
4.   
5. print "a variable in t2 is %s" % t2.var_in_t2  
6. print "now will call a function in t2"  
7. print ">>>t2.func_in_t2"  
8. t2.func_in_t2()  
9.   
10. #t2.py  
11.   
12. var_in_t2 = 0  
13. def func_in_t2():  
14. print "this is a function in t2.py"  
15.   
16. print "execution of t2.py"  
17.   
18. #执行import t1后的输出结果  
19.   
20. execution of t2.py  
21. a variable in t2 is 0  
22. now will call a function in t2  
23. >>>t2.func_in_t2  
24. this is a function in t2.py


并且import具有层次性质,即在t1中import t2模块,在t1的上一级是不能访问到t2中的对象的。


[python]  view plain copy


1. >>> t2.var_in_t2  
2.   
3. Traceback (most recent call last):  
4. "<pyshell#4>", line 1, in <module>  
5.     t2.var_in_t2  
6. NameError: name 't2' is not defined  
7.   
8. #在执行import t1后,在t1中import t2,所以t1中可以访问t2中的变量,但是在t1之外,例如,全局环境下试图访问t2.var_in_t2,报错。


从上例中可以看出在t1中建立了一个命名空间t2,通过这个命名空间,t1可以访问t2模块中定义的变量和函数。并且这个命名空间只对t1放开的。如果想在全局环境下使用t2中的对象,必须import t2.

from ... import ...

如果使用from xxx.py import xxx.varorfun,则有所不同,会在当前环境中建立和变量varorfun同名的对象,并且将xxx.py内的变量赋值给它们,这样在当前环境中直接访问该变量了,不需要加模块名。

package:

包一般是目录,内含python模块或者子目录,同时一般还包含一个__init__.py的python文件,在这个init文件中初始化首次import package时执行的模块。例如,


[python]  view plain copy


1. Package1/ __init__.py  
2.                  Module1.py  
3.                  Module2.py  
4.                  Package2/ __init__.py  
5.                                   Module1.py  
6.                                   Module2.py



表示一个包Package1,它包含__init__py、Module1.py、Module2.py,同时包含子目录Package2及其模块。当需要导入包时,例如import Package1,就会执行Package1下的__init__.py文件。如果想在导入包是做些初始化的工作,例如导入其他的包等等,都可以在这里文件里写。其中有个list变量__all__,它包含当执行from Package1 import *时导入的包名,例如__all__ = ["Module1", "Module2"]。如果只想单独导入某个模块,可以这样:import Package1.Module1、import Package1.Package2.Module2。


os.getcwd()函数。

sys.path[0]变量或者sys.argv[0]来获得。实际上sys.path是Python会去寻找模块的搜索路径列表,sys.path[0]和sys.argv[0]是一回事因为Python会自动把sys.argv[0]加入sys.path。

具体来说,如果你在C:\test目录下执行python getpath\getpath.py,那么os.getcwd()会输出“C:\test”,sys.path[0]会输出“C:\test\getpath”。

更特别地,如果你用py2exe模块把Python脚本编译为可执行文件,那么sys.path[0]的输出还会变化:

  1. 如果把依赖库用默认的方式打包为zip文件,那么sys.path[0]会输出“C:\test\getpath\libarary.zip”;
  2. 如果在setup.py里面指定zipfile=None参数,依赖库就会被打包到exe文件里面,那么sys.path[0]会输出“C:\test\getpath\getpath.exe”。

模块的搜索路径

模块的搜索路径都放在了sys.path列表中,如果缺省的sys.path中没有含有自己的模块或包的路径,可以动态的加入(sys.path.apend)即可。下面是sys.path在Windows平台下的添加规则。

1、sys.path第一个路径往往是主模块所在的目录。在交互环境下添加一个空项,它对应当前目录。

2、如果PYTHONPATH环境变量存在,sys.path会加载此变量指定的目录。

3、我们尝试找到Python Home,如果设置了PYTHONHOME环境变量,我们认为这就是Python Home,否则,我们使用python.exe所在目录找到lib/os.py去推断Python Home。

如果我们确实找到了Python Home,则相关的子目录(Lib、plat-win、lib-tk等)将以Python Home为基础加入到sys.path,并导入(执行)lib/site.py,将site-specific目录及其下的包加入。

如果我们没有找到Python Home,则把注册表Software/Python/PythonCore/2.5/PythonPath的项加入sys.path(HKLM和 HKCU合并后加入),但相关的子目录不会自动添加的。

4、如果我们没有找到Python Home,并且没有PYTHONPATH环境变量,并且不能在注册表中找到PythonPath,那么缺省相对路径将加入(如:./Lib;./plat-win等)。

总结如下

当在安装好的主目录中运行Python.exe时,首先推断Python Home,如果找到了PythonHome,注册表中的PythonPath将被忽略;否则将注册表的PythonPath加入。

如果PYTHONPATH环境变量存在,sys.path肯定会加载此变量指定的目录。

如果Python.exe在另外的一个目录下(不同的目录,比如通过COM嵌入到其他程序),Python Home将不推断,此时注册表的PythonPath将被使用。

如果Python.exe不能发现他的主目录(PythonHome),并且注册表也没有PythonPath,则将加入缺省的相对目录。

 

标准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。

另外一种嵌套指,在模块A中import B,而在模块B中import A。这时会怎么样呢?这个在Python列表中由RobertChen给出了详细解释,抄录如下:



[A.py]  
from B import D  
class C:pass  

[B.py]  
from A import C  
class D:pass



为什么执行A的时候不能加载D呢?

如果将A.py改为:import B就可以了。

这是怎么回事呢?

RobertChen:这跟Python内部import的机制是有关的,具体到from B import D,Python内部会分成几个步骤:

  1. 在sys.modules中查找符号"B"
  2. 果符号B存在,则获得符号B对应的module对象<module B>。 从<module B>的__dict__中获得符号"D"对应的对象,如果"D"不存在,则抛出异常
  3. 如果符号B不存在,则创建一个新的module对象<module B>,注意,这时,module对象的__dict__为空。 执行B.py中的表达式,填充<module B>的__dict__ 。 从<module B>的__dict__中获得"D"对应的对象,如果"D"不存在,则抛出异常。

所以,这个例子的执行顺序如下:

1、执行A.py中的from B import D

由于是执行的python A.py,所以在sys.modules中并没有<module B>存在,首先为B.py创建一个module对象(<module B>),注意,这时创建的这个module对象是空的,里边啥也没有,在Python内部创建了这个module对象之后,就会解析执行B.py,其目的是填充<module B>这个dict。

2、执行B.py中的from A import C

在执行B.py的过程中,会碰到这一句,首先检查sys.modules这个module缓存中是否已经存在<module A>了,由于这时缓存还没有缓存<module A>,所以类似的,Python内部会为A.py创建一个module对象(<module A>),然后,同样地,执行A.py中的语句。

3、再次执行A.py中的from B import D

这时,由于在第1步时,创建的<module B>对象已经缓存在了sys.modules中,所以直接就得到了<module B>,但是,注意,从整个过程来看,我们知道,这时<module B>还是一个空的对象,里面啥也没有,所以从这个module中获得符号"D"的操作就会抛出异常。如果这里只是import B,由于"B"这个符号在sys.modules中已经存在,所以是不会抛出异常的。

Package(包) Import

包(Package)可以看成模块的集合,只要一个文件夹下面有个__init__.py文件,那么这个文件夹就可以看做是一个包。包下面的文件夹还可以成为包(子包)。更进一步,多个较小的包可以聚合成一个较大的包,通过包这种结构,方便了类的管理和维护,也方便了用户的使用。比如SQLAlchemy等都是以包的形式发布给用户的。

包和模块其实是很类似的东西,如果查看包的类型import SQLAlchemy type(SQLAlchemy),可以看到其实也是<type 'module'>。import包的时候查找的路径也是sys.path。

包导入的过程和模块的基本一致,只是导入包的时候会执行此包目录下的__init__.py而不是模块里面的语句了。另外,如果只是单纯的导入包,而包的__init__.py中又没有明确的其他初始化操作,那么此包下面的模块是不会自动导入的。如:

PA

--__init__.py
 --wave.py
 --PB1
   --__init__.py
   --pb1_m.py
 --PB2
   --__init__.py
   --pb2_m.py

__init__.py都为空,如果有以下程序:



 



    1. import sys
    2. import PA.wave  #1
    3. import PA.PB1   #2
    4. import PA.PB1.pb1_m as m1  #3
    5. 
    6. import PA.PB2.pb2_m #4
    7. 
    8. PA.wave.getName() #5
    9. 
    10. m1.getName() #6
    11. 
    12. PA.PB2.pb2_m.getName() #7



    当执行#1后,sys.modules会同时存在PA、PA.wave两个模块,此时可以调用PA.wave的任何类或函数了。但不能调用PA.PB1(2)下的任何模块。当前Local中有了PA名字。

    当执行#2后,只是将PA.PB1载入内存,sys.modules中会有PA、PA.wave、PA.PB1三个模块,但是PA.PB1下的任何模块都没有自动载入内存,此时如果直接执行PA.PB1.pb1_m.getName()则会出错,因为PA.PB1中并没有pb1_m。当前Local中还是只有PA名字,并没有PA.PB1名字。

    当执行#3后,会将PA.PB1下的pb1_m载入内存,sys.modules中会有PA、PA.wave、PA.PB1、PA.PB1.pb1_m四个模块,此时可以执行PA.PB1.pb1_m.getName()了。由于使用了as,当前Local中除了PA名字,另外添加了m1作为PA.PB1.pb1_m的别名。

    当执行#4后,会将PA.PB2、PA.PB2.pb2_m载入内存,sys.modules中会有PA、PA.wave、PA.PB1、PA.PB1.pb1_m、PA.PB2、PA.PB2.pb2_m六个模块。当前Local中还是只有PA、m1。

    下面的#5,#6,#7都是可以正确运行的。

    注意的是:如果PA.PB2.pb2_m想导入PA.PB1.pb1_m、PA.wave是可以直接成功的。最好是采用明确的导入路径,对于./..相对导入路径还是不推荐用。