Python 的 import 机制 《Python源码剖析》笔记

在Python中,是通过import来导入模块和包,那么,import的机制是什么呢?

其实,在启动python环境时,python就会自动的导入许多的模块,这些模块被加载到了内存中,然而,为了保证当前命名空间的干净(也就是加载的模块的命名和当前命名空间的命名不冲突),python规定,只有当用户显式的通过 import 机制通知 python,才可以在当前的命名空间中使用模块。

首先介绍一下python的 dir 函数。如果该函数没有参数,则返回的是当前命名空间的所有符号,若有参数,则将该参数作为对象,输出该对象的所有属性。

Python内建的module

在启动IDLE后,python运行环境就会进行初始化,首先我们来看下面的代码:
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
>>> import sys
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'sys']
>>> type(sys)
<type 'module'>
>>> 
通过上面的代码,可以看到,初始的python环境中,命名空间中有一些默认的名字,当通过 import 导入 sys module中,发现‘sys’出现在了当前的命名空间中,且type(sys)这条语句,一方面说明了程序中已经可以使用 'sys' 了,而通过其结果也可以看到,sys的类型确实是模块。
上面说到,在启动python环境时,会加载进许多的模块,而这些模块,正是存在sys.modules 中。实际上,sys.modules 是一个字典类型,存的数据类型是key,value 。其中key值是所加载模块的名字,value是模块的路径。通过下面的程序可以得到默认加载的所有模块。
>>> def show_modules():
	for item in sys.modules.items():
		print item

		
>>> show_modules()
部分结果:
('_tkinter', <module '_tkinter' from 'C:\Python27\DLLs\_tkinter.pyd'>)
('bdb', <module 'bdb' from 'C:\Python27\lib\bdb.pyc'>)
('re', <module 're' from 'C:\Python27\lib\re.pyc'>)
为了确认sys.modules中的模块和我们显式导入的模块是同一个模块,通过 id() 来验证下:
>>> id(sys.modules['os'])
23066832
>>> import os
>>> id(os)
23066832
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'os', 'show_modules', 'sys']
>>> 
通过id()函数所获得的结果一样,说明显式导入的os模块就是sys.modules中的os模块。而且,现在运行dir()函数,命名空间中也出现了os、show_modules。

用户自定义的模块

python支持使用自定义的模块,同样是通过import来导入,一个简单的.py文件就可以作为一个模块。
假如有一个hello.py,里面的内容是 a = 1   b = 2
我们看下面的代码
import sys
def containHello():
    return 'hello' in sys.modules.keys()

print containHello()
返回的结果是: False
当我们显式的import hello 时,结果就会发生改变:
import hello
print containHello()
结果是:True
且此时运行dir(),返回的结果是:
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'containHello', 'hello', 'sys']
说明hello 已经在当前的命名空间中了。
我们再来验证一下:
print id(hello)
print id(sys.modules['hello'])
结果为:
31516368
31516368
print type(hello)
结果为:
<type 'module'>
说明:import 机制确实是新创建了一个名称为hello的module,而且这个名称为hello的module,被储存在了sys.modules中。
我们在来看下hello的一些信息:
print dir(hello)
print hello.__dict__.keys()
print hello.__name__
print hello.__file__
输出结果分别为:
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b']
['a', 'b', '__builtins__', '__file__', '__package__', '__name__', '__doc__']
hello
D:\JackieCodeProject\python\importtest\hello.pyc
hello是一个module对象,因此我们可以知道,module对象内部实际上也是通过一个字典在维护,key值是该module的一些信息的名称,value值是这些信息的值。比如说该module,通过其__name__和__file__,就可以得到该module的一些信息。
查看hello所在的位置,会发现存在一个hello.pyc,说明在import的过程中,hello.py被编译执行了,这一点需要特别注意。
你你好你好你好呵呵号啊
再来观察,在hello的命名空间中存在"__builtins__",而在刚初始化的python环境中,也存在“__builtins__”,这两者存在联系吗?用代码来验证一下:
print type(__builtins__)
print id(__builtins__)
print type(hello.__builtins__)
print id(hello.__builtins__)
结果为:
<type 'module'>
23119632
<type 'dict'>
23096608
可以发现,二者没有直接的联系,且类型也不一样。
然而,再通过下面的代码验证下:
print id(hello.__builtins__)
print id(__builtins__.__dict__)
print id(sys.modules['__builtin__'].__dict__)
结果为:
23096608
23096608
23096608
可以发现,其实,hello 这个module中的__builtins__正是当前命名空间中__builtins__符号对应的module对象所维护的那个dict(字典)对象。而其实他们两个都是表象,实际上是在sys.modules中存在有一个__builtin__模块,是该模块维护的一个__dict__。

嵌套import

当在python程序中执行 import A,而在 A.py 中还存在有 import B 这样的语句时,就发生了嵌套import,那么这时的情况是什么样呢?
usermodule1.py
import usermodule2
usermodule2.py
import sys
我们在usermodule.py 中import  usermodule1.py
print dir()
import usermodule1
print dir()
print dir(usermodule1)
print dir(usermodule1.usermodule2)
结果分别为:
['__builtins__', '__doc__', '__file__', '__name__', '__package__']
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'usermodule1']
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'usermodule2']
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'sys']
可以发现,在usermodule1.py 和 usermodule2.py 中进行的import 的动作并没有影响到上一层的名字空间,而只是影响到了各个module自身的名字空间,更准确的说,是影响到了各个module所维护的那个dict对象。
在上面的例子中,当import 进hello后,sys.modules中会增加一个hello module,那么在本例中,usermodule2会不会增加进sys.modules中呢?
import sys
print sys.modules['usermodule2']
结果:
<module 'usermodule2' from 'D:\JackieCodeProject\python\importtest\usermodule2.pyc'>
实际上,所有的import动作,不论发生在什么时间,什么地方,都会影响到全局的module集合,也就是sys.modules。这样做的一个好处是,当程序中多次import 某个module时,可以直接引入已经import过得module,节省时间。

import package

python提供了另外一种管理module的机制,package机制。
在python中,逻辑上相关联的class被聚合到一个module中,同理,逻辑上相关联的module被聚合到一个package中。python使用package的方法也是通过import。
首先,我们创建一个package,就是创建一个文件夹,再在文件夹下创建一个.py文件。(注意创建的文件夹路径在:C:\Python24\Lib\idlelib\A,即python安装环境下)
所创建的package为A,有一个名为tank的py文件,这时就可以直接import A 了。
然而,无论是import A  还是 import A.tank 都会提示错误。
原来,python规定,只有在文件夹中有一个特殊的__init__.py文件,python才将该文件夹作为一个package,这个__init__.py 可以为空。加入__init__.py,看下面的代码:
>>> dir()
['__builtins__', '__doc__', '__name__']
>>> import A
>>> dir()
['A', '__builtins__', '__doc__', '__name__']
>>> dir(A)
['__builtins__', '__doc__', '__file__', '__name__', '__path__']
>>> import A.tank
>>> dir(A)
['__builtins__', '__doc__', '__file__', '__name__', '__path__', 'tank']
>>> import sys
>>> id(A)
31008560
>>> id(sys.modules['A'])
31008560
>>> id(A.tank)
31008752
>>> id(sys.modules['tank'])

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in -toplevel-
    id(sys.modules['tank'])
KeyError: 'tank'
>>> id(sys.modules['A.tank'])
31008752
>>> 
发现,A可以被加载,且加载后A在命名空间中出现了,同样对于A.tank。
可以重新试一下,直接import A.tank,会发现结果和上面的一样。也就是说,虽然代码中直接import 了 A.tank,然而,A也被加载了进来,而且,对于tank的访问必须通过A.tank来访问。这样有一个好处是,当有一个B package,且B里面也有同样的一个tank.py时,在程序中可以通过A.tank 和 B.tank进行区分,不会混淆。

在__init__.py 中加入:print "hello import A"   且在A下新建一个 airplane.py 文件。
>>> import A.tank
hello import A
>>> dir(A)
['__builtins__', '__doc__', '__file__', '__name__', '__path__', 'tank']
>>> import A.airplane
>>> dir(A)
['__builtins__', '__doc__', '__file__', '__name__', '__path__', 'airplane', 'tank']
>>> A.__path__
['C:\\Python24\\Lib\\idlelib\\A']
>>> 
可以发现,在第一次import A.tank时,由于python同时会加载A,所以输出了__init__.py中的语句,第二次 import A.airplane时,没有输出__init__.py中的语句,也就是说没有重新的加载A。这是由python加载package的机制所决定的。
当python加载A.airplane时,会首先在sys.modules中搜索A是否被加载了,如果被加载了,则会通过A的元信息__path__找到A的路径,然后再A的路径下查找airplane,所以不会第二次加载A。

from 与 import

python还提供了一种精确加载的方法,就是通过from与import的结合,可以直接使用我们想要的module.
比如说在上面的例子中,我们把程序改为:
>>> from A import tank
hello import A
>>> dir()
['__builtins__', '__doc__', '__name__', 'tank']
>>> 
这样,tank就直接出现在了命名空间中,也就是说,我们可以直接使用tank了,而不需要通过A.tank这样的方式。
然而,再看下面的程序:
>>> import sys
>>> sys.modules['tank']

Traceback (most recent call last):
  File "<pyshell#4>", line 1, in -toplevel-
    sys.modules['tank']
KeyError: 'tank'
>>> sys.modules['A']
<module 'A' from 'C:\Python24\Lib\idlelib\A\__init__.pyc'>
>>> sys.modules['A.tank']
<module 'A.tank' from 'C:\Python24\Lib\idlelib\A\tank.pyc'>
>>> 
可以发现,实际上,在机制上面,import 和 from import 并没有多大的区别,都同样加载了A,只不过是引入到当前命名空间的module不一样,其在底层实现上是一样的。
Over





版权声明:本文为u013307828原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。