刚开始用Python写代码,对于import的方式不是很理解,尤其是导入上层包的模块时,经常会使用sys.path.append('..'),然后再import,很不优雅,所以花些时间全部梳理一遍。

首先需要明白明白两个概念模块,很多人也都了解,贴出runoob(页面跟W3C好像,他们有什么关系吗?)上给出的两个定义。

  • 模块

    Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句。
    模块让你能够有逻辑地组织你的 Python 代码段。
    把相关的代码分配到一个模块里能让你的代码更好用,更易懂。
    模块能定义函数,类和变量,模块里也能包含可执行的代码。

  • 包是一个分层次的文件目录结构,它定义了一个由模块及子包,和子包下的子包等组成的 Python 的应用环境。
    简单来说,包就是文件夹,但该文件夹下必须存在 __init__.py 文件, 该文件的内容可以为空。__int__.py用于标识当前文件夹是一个包。

从顶层模块(sys.path)或者当前的包中导入模块,使用import module或者from module import xxx的方式就不再赘述,不过有一种使用括号进行的多模块导入方式,如:

1
2
from from Tkinter import (Tk, Frame, Button, Entry, Canvas, Text,
LEFT, DISABLED, NORMAL, RIDGE, END)

这样可以避免使用反斜杠续行,稍微优雅一些,多行的字符串也可以使用一样的括号语法实现。记住,from module import *是绝对的不推荐的,会意外地“污染”命名空间。

相对导入和绝对导入是这次讨论的重点,在项目中,肯定不意外地会建立不同层级和结构关系的包,那么在这些包之间,我们又应该如何导入要引用的模块?

绝对导入

在Python 2.4之前,如果使用了import foo,如果当前包内也有一个模块foo,由于解释器是不知道你导入的是顶层模块还是当前包内的模块,根据模块寻找加载的顺序[],当前包内的foo模块就会覆盖掉顶层的foo模块,而可能引起不必要的歧义。于是要求foo必须是在sys.path中能够寻到的模块或包,这就是绝对导入的定义。python-dev 社区选择将绝对导入作为默认导入的方式,一来是因为更常用,二是因为绝对导入可以提供相对导入的全部功能.

在Python 2.5 和 2.6中,绝对导入是可选的,需要在文件开头添加from __future__ import absolute_import来实现默认的绝对导入。

相对导入

相对导入是根据模块的__name__属性来决定模块的位置,然后计算相对路径,在同一个项目相邻层级的包和模块中最为常用,也最为方便。关于相对导入的语法实现模式,有一个很有趣也很广泛的讨论,看着那些先驱开发者的讨论,收获也是蛮多的,感兴趣的参考PEP 328 – Imports: Multi-Line and Absolute/Relative。最后,Guido采用”.”前缀表示相对导入,和Unix系统中的目录表示含义一样,一个”.”表示当年层级,多一个就表示向上一层。下面是一些例子:

1
2
3
4
5
6
7
8
from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo
from ...package import bar
from ...sys import path

区别

相对导入必须使用 from .<> import xxx(并且至少含有一个.符号)的模式,import <>总是绝对导入,当然,如果from <> import中没有句点,那么也是绝对导入。但是类似import .foo的方式是非合法语句,因为

1
import XXX.YYY.ZZZ


1
XXX.YYY.ZZZ

是可用的表达式,而

1
import .moduleY


1
.moduleY

不是一个可用的表达式。

如何选择

使用相对导入还是绝对导入,可以完全凭个人喜好。但是为了代码的可阅读性,一般的共识是不超过两层的层级可以使用相对导入,试想一下,如果有超过3个的”.”,还能够正确快速地知道向上到了哪个层级吗?这个时候使用绝对导入会更加方便,代码也更好维护。不论哪一种导入方式,也都会有重构时的麻烦(如重命名一个模块),但是我想在IDE如此普遍使用的情况下,这个因素应该是最不值得考虑的了。加一个TODO吧,参阅一些优秀的开源框架源码,看看他们是如何组织的。

后话

使用了相对导入以后,如果直接以python script_name.py的方式运行脚本,Python解释器会报

1
2
#python 2
ValueError: Attempted relative import in non-package

或者

1
2
#python 3
SystemError: Parent module '' not loaded, cannot perform relative import

的错误,可以使用python -m script_name.py加上-m开关的方式运行,-m用于告诉解释器以脚本的方式运行模块,具体这样做的原因将在下一篇文章中分析。

参考

  1. Python 相对导入与绝对导入
  2. http://www.runoob.com/python/python-modules.html
  3. PEP 328 – Imports: Multi-Line and Absolute/Relative
  4. Python导入模块的几种姿势