Python中的导入

参考《Python学习手册》,强烈建议看下这本书的相关章节。

在一些规模较大的项目中,经常可以看到通过imp、__import__导入module的例子。通过这些方法,代码可以灵活的根据需要(如配置文件)导入具体的模块。这篇文章小秦会总结一下python导入的基本知识和几种用法。

1.import的过程
比如下面这个例子:

import abc

这行代码会的导入abc,导入的过程是:在sys.path这个list中的路径中,按照顺序搜索名字符合要求的文件。这些文件可以是.py、.pyc或其他如zip这类文件。如果搜索到的文件是非字节码的文件,那么python会的先把它编译成字节码然后导入。如果同时存在字节码和非字节码的文件,则会的比较两者的修改时间戳来决定是否要重新编译生成字节码文件(所以一般的发行版可执行文件都是字节码格式的,这样可以加快导入的速度)。找到确定的字节码文件后,python解析器会的加载该文件,文件中的所有语句会的从顶至下执行一遍,这时这个文件中所有对全局变量的赋值都会得到对应的引用对象(def、class这类语句其实也是赋值)。这些变量都属于该导入模块的名字空间中。最后这个模块文件整体被赋值到import语句指明的那个变量中,在这里就是我们的abc。此时abc就引用了我们的abc模块。

关于文件的搜索路径有一点要说明的是,所有的python解析器会的自动import site模块,这个模块会的生成我们的sys.path变量,这其中包含了对.pth文件的解析。.pth里边可以包含指向某个搜索路径的文本,解析器解析了.pth文件后会自动去其写明的路径继续搜索模块。默认情况下,sys.path的构成顺序是:程序的主目录,PYTHONPATH环境变量指定的目录,标准链接库目录和.pth文件中写明的目录。

上面也说了,搜索文件的时候目标文件可以是.py、.pyc或其他如zip这类文件。如果说同时有abc.py,abc.zip存在,那么会的按照一定的顺序(比如.py优先于.zip)加载第一个符合要求的文件。

2.import时候的from
比如下面这个例子:

from a import b
#or
import a.b as b

这两行代码都从a中导入b,并赋值给b变量。看上去作用一样,实际上有一定的区别,区别在于:
a.import a.b as b中的a、b必须是一个package或module,而from则不用。换句话说,from可以直接获取属性(可以认为上面的语句的意思是先生成a对象,然后将a的b属性赋值给本地名字空间的b变量)。
b.from会的将b绑定到一个独立的对象上,这个对象在reload的时候不会的收到影响,而import则在reload的时候会的将b指向新的对象上。

3.__import__
import会的调用__import__执行实际的导入工作。一般只有在运行代码期间才知道被导入模块名字的时候才会使用这个内置方法。具体的语法可以看:https://docs.python.org/2/library/functions.html?highlight=__import__#__import__

这个方法在很多的项目里都有看到过,比如动态的导入基于抽象类(使用abc的meta元类)的某个实现的backend的时候会用到。

4.imp
imp模块可以实现import的所有功能,并且方便使用。比如saltstack在导入各种module的时候,就是通过imp来导入的。官方文档是https://docs.python.org/2/library/imp.html,可以在里边找到该模块的方法。

两个重要的方法是find_module和load_module,前者用于找到需要被导入module的具体名字,后者执行真正的导入操作。另外该模块还有如new_module这类方法。

这是官方文档中的例子:

import imp
import sys

def __import__(name, globals=None, locals=None, fromlist=None):
    # Fast path: see if the module has already been imported.
    try:
        return sys.modules[name]
    except KeyError:
        pass

    # If any of the following calls raises an exception,
    # there's a problem we can't handle -- let the caller handle it.

    fp, pathname, description = imp.find_module(name)

    try:
        return imp.load_module(name, fp, pathname, description)
    finally:
        # Since we may exit via an exception, close fp explicitly.
        if fp:
            fp.close()

5.sys.modules
所有被导入的模块都会的插入到sys.modules这个字典中,字典的key是模块名,字典的value则是具体的模块对象,比如:

>>> import a
>>> import sys
>>> print sys.modules['a']
<module 'a' from 'a/__init__.pyc'>
>>> print a
<module 'a' from 'a/__init__.pyc'>
>>> id(a)
4465092264
>>> id(sys.modules['a'])
4465092264

正是由于这个字典的存在,在多个文件中import同一个模块是不会重复导入的,因此导入的时候会的先查看sys.modules中是否存在对应的模块,只有不存在的时候才会的被导入。这个特性在某些时候不需要(比如evenelet中的绿化),因此可以先将这个字典中的某个项del掉,然后导入新的模块。

6.绝对导入和相对导入
大部分的导入用的都是绝对导入,小秦很少见到用相对导入的代码。

绝对导入就是按照sys.path进行文件搜索的导入,而相对导入则是from中通过’.’在包中搜索对应文件的导入。通过增加’.’能保证导入只是在包中进行搜索,不会在sys.path中进行搜索。另外相对导入的搜索路径是包含导入语句的文件所在的那个目录。

比如这个例子:

[/tmp/a]$ls
__init__.py b
[/tmp/a]$ls b/
__init__.py x.py        y.py
[/tmp/a]$cat b/x.py 
from __future__ import absolute_import
from . import y
[/tmp/a]$cat b/y.py 
print('y is imported')

在a目录中,执行下面的代码可以看到y被导入了:

>>> import b.x
y is imported

如果我们将x.py的内容换成:

[/tmp/a]$cat b/x.py
from __future__ import absolute_import
import y

那么我们在执行导入的时候就会看到异常:

>>> import b.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "b/x.py", line 2, in <module>
    import y
ImportError: No module named y

除了’.’外,相对导入的’..’表示从文件的上层导入,’.a’表示从文件的所在路径的a模块导入。’..a’表示从上层的a模块导入。借用《Python学习手册》中的例子:
对于A.B.C这个模块中的下面的这些代码,具体的导入文件是:

from . import D    #导入的是A.B.D
from .. import E   #导入的是A.E

from .D import X   #导入的是A.B.D.X
from ..E import X  #导入的是A.E.X

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*