模块搜索路径
当执行 import foo 时,Python 需要找到名为 foo.py 的文件。这个查找过程遵循一套明确的规则,理解这些规则对解决 ImportError 至关重要。
sys.path
Python 维护一个名为 sys.path 的列表,按顺序存储模块搜索路径:
import sys
for p in sys.path:
print p
# 典型输出:
# '' (空字符串,表示当前目录)
# '/usr/lib/python2.7'
# '/usr/lib/python2.7/plat-linux2'
# '/usr/lib/python2.7/lib-tk'
# '/usr/lib/python2.7/lib-old'
# '/usr/lib/python2.7/lib-dynload'
# '/usr/local/lib/python2.7/dist-packages'
# '/usr/lib/python2.7/dist-packages'
Python 按 sys.path 的顺序逐个目录查找,第一个匹配的模块被加载,后续目录被忽略。
搜索路径的来源
sys.path 由以下部分组成:
- 当前脚本所在目录(或空字符串表示当前工作目录)
PYTHONPATH环境变量中的目录- 标准库目录
- site-packages 目录(第三方包)
# Linux/macOS:设置 PYTHONPATH
export PYTHONPATH=/home/user/mylibs:/home/user/morelibs
python myscript.py
# Windows:设置 PYTHONPATH
set PYTHONPATH=C:\Users\User\mylibs;C:\Users\User\morelibs
python myscript.py
修改搜索路径
可以在运行时修改 sys.path:
import sys
sys.path.insert(0, "/path/to/modules") # 插入到开头,优先查找
sys.path.append("/path/to/more") # 追加到末尾
临时修改常用于:
- 加载不在标准位置的模块
- 测试新版本的库
- 插件系统动态加载
同名模块的冲突
如果多个目录中有同名模块,先找到的优先:
# 当前目录下有 random.py
import random
print random.__file__ # 可能加载了当前目录的 random.py,而非标准库!
这是 Python 2 中常见的 bug:给脚本起名为 random.py、string.py、test.py 等,会覆盖标准库模块。
解决方法:
- 不要用标准库模块名作为脚本名
- 检查
module.__file__确认加载的是哪个文件
import random
print random.__file__ # /usr/lib/python2.7/random.pyc
内置模块
有些模块(如 sys、__builtin__)是内置的,没有对应的 .py 文件:
import sys
print sys.__file__ # AttributeError: 'module' object has no attribute '__file__'
import math
print math.__file__ # /usr/lib/python2.7/lib-dynload/math.so(C 扩展)
相对导入与绝对导入
Python 2 默认允许相对导入:如果当前包中有 foo.py,import foo 会优先加载它而非标准库的 foo。这在大型项目中容易出错。
Python 2.7 支持 from __future__ import absolute_import 来启用绝对导入:
from __future__ import absolute_import
import string # 总是导入标准库的 string
from . import string # 显式相对导入,当前包的 string
这是 Python 3 的默认行为,建议 Python 2.7 项目也启用。
查找模块文件
可以用 imp 模块查找模块位置:
import imp
print imp.find_module("os") # (None, '/usr/lib/python2.7/os.py', ('.py', 'U', 1))
print imp.find_module("sys") # (None, 'sys', ('', '', 6)) —— 内置模块