Python模块与包导入机制全解析:查找顺序、命名空间与文件类型
本文深入解析Python模块与包的导入机制,详细阐述sys.path的查找优先级、__init__.py在包组织中的作用,以及import与from...import语句的区别。同时涵盖相对导入的限制、命名空间(LEGB)规则及常见文件类型,帮助开发者快速定位模块导入错误并优化代码结构。
简介
无论是为了阅读他人的代码,还是为了更合理地组织个人工程项目,深入理解Python的模块和包机制都是必不可少的。
此外,掌握模块搜索的具体顺序,有助于我们深入理解常见的导入错误,从而能够快速定位并解决问题。
模块导入查找顺序
首先,我们来梳理Python解释器查找模块的具体流程:
- 1. 检查sys.modules缓存(已导入模块)
- 2. 执行脚本所在的目录(即当__name__等于'__main__'时)
- 3. 环境变量PYTHONPATH指定的目录列表
- 4. Python安装根目录下的Lib标准库目录
- 5. 配置文件(Python安装根目录下.pth文件中指定的目录)
- 6. 第三方库目录(Python安装根目录下site-packages中的目录)
搜索路径的具体内容可以通过sys.path变量查看,我们可以打印该变量来确认:
import sys print(sys.path)
输出示例如下:
['E:\\app\\python\\fin\\md', 'E:\\app\\python\\fin', 'E:\\app\\python\\fin\\.venv\\Scripts\\python313.zip', 'D:\\Env\\python\\py3130i\\DLLs', 'D:\\Env\\python\\py3130i\\Lib', 'D:\\Env\\python\\py3130i', 'E:\\app\\python\\fin\\.venv', 'E:\\app\\python\\fin\\.venv\\Lib\\site-packages']
我们可以手动向sys.path中添加搜索目录,并控制其查找优先级:
import sys
# 添加在末尾,最后查找
sys.path.append('dir')
# 添加在开头,最先查找
sys.path.insert(0, 'dir2')
# 支持zip
sys.path.append('module.zip')
# 支持zip中指定目录
sys.path.append('module.zip/lib/python')
模块查找的具体路径来源
入口文件所在目录

环境变量PYTHONPATH
- 在Linux系统中:
echo 'export PYTHONPATH="/home/tim/pylib/"' >> ~/.bashrc source ~/.bashrc

- 在Windows系统中:

Lib目录(标准库)

.pth配置文件
需要注意._pth文件和.pth文件的区别。
._pth文件:在常规安装包中不存在,需自行创建;但在某些内嵌包环境中存在。

其内容示例如下:
python311.zip . D:\\Env\\python\\py3114\\Lib\\site-packages\\ # Uncomment to run site.main() automatically #import site
._pth文件会完全覆盖sys.path。
而.pth文件则是向sys.path中添加路径。
.pth文件的处理逻辑位于标准库site模块中:

新版本的.pth文件存在诸多限制,具体可参考site模块文档。
注意事项:
该文件的名字必须准确无误,如果不确定,可以直接复制安装目录上PythonXXX.dll文件的名字。
另外,通常不建议修改此文件,因为它会覆盖sys.path,导致其他路径配置失效。
三方库
标准库Lib下的site-packages目录

模块
在Python中,模块通常指一个.py文件,每个文件代表一个独立的模块。
包
很多时候,一个工程项目包含多个模块,我们可以使用包来组织不同的模块。
包中包含一个__init__.py文件,用于控制包的导入行为。
在Python 2.x中,__init__.py文件是必须存在的;而在3.x中则是可选的。
import语句
import 模块名
Python解释器在第一次导入模块时会将其加载到内存中,多次执行import语句不会重新执行模块内的代码。
我们看一个示例:
假设我们有一个base_module.py文件。
print('这是一个自定义基本模块')
num = 1000
def fun_a():
print('fun_a', num)
def fun_b():
print('fun_b')
if __name__=='__main__':
print('执行了该文件')
我们在module_import_test.py文件中导入base_module模块(即文件)。
import sys print(sys.modules) import base_module # 多了base_module模块信息 print(sys.modules) base_module.fun_a() import base_module print(sys.modules) base_module.fun_a()

注意:文件名不要使用中划线,否则import模块时会出错。
我们可以看到没有打印“执行了该文件”,这是因为我们导入的是模块,而不是直接执行这个文件。
现在是不是就更清楚为什么要加上:if __name__=='__main__':这段代码了。
if __name__=='__main__':
print('执行了该文件')
from mmm import fffvvv
从包或者模块中导入函数、变量等特定元素。
与直接import模块不同,from方式相当于将模块指定的元素导入到当前命名空间。
因此,我们可以直接调用方法,不用通过模块名.xxx的方式调用了。
from base_module import fun_a,fun_b fun_a() fun_b()

from mmm import fff as new_name
还可以使用as关键字进行重命名:
from base_module import fun_a as xxx xxx()
from mmm import *
*表示将模块中除了下划线(_)开头的元素全部导入。
from base_module import * fun_a() fun_b() print(num)
__all__控制import *的行为
我们在模块中添加__all__,注意是一个字符串列表,下面是fun_a。
print('这是一个自定义基本模块')
num = 1000
def fun_a():
print('fun_a', num)
def fun_b():
print('fun_b')
__all__ = ['fun_a']
if __name__=='__main__':
print('执行了该文件')
我们再调用fun_b(),就会出现NameError错误:

相对导入与绝对导入
- pkg_b.py
num_b = "num_b"
def fun_b():
print("pkg_b fun_b")
- pkg_c.py
from .pkg_b import fun_b
from . import pkg_b
num_c = "num_c"
def fun_c():
print("pkg_c fun_c")
fun_b()
print(f"pkg_c中导入pkg_b:{pkg_b.num_b}")
- base_import.py
import pkg_test.sub_pkg.pkg_b from pkg_test.sub_pkg import pkg_b from pkg_test.sub_pkg import pkg_c pkg_test.sub_pkg.pkg_b.fun_b() pkg_b.fun_b() pkg_c.fun_c() pkg_c.pkg_b.fun_b()

相对导入的模块不能作为主程序运行,因为.会被替换为__main__,然后就会出现:
ImportError: attempted relative import with no known parent package

小结
导入包和导入模块基本一致,如何判断是包还是模块呢?
包通常是有结构,所以,看到import xxx.yyy这种包含.的import,就是导入的包。
import 包名.模块名 as 别名 from . import 模块名 from 包名 import 模块名 as 别名 from 包名.模块名 import 成员名 as 别名
更多内容可以参考:
官方import说明
包说明
命名空间
Python的命名空间有点类似于变量表。
- local namespace,就是函数的命名空间,相当于局部变量表,包含函数的变量
- global namespace,就是模块的命名空间,包含functions、classes、modules、变量、常量等
- build-in namespace,包含build-in function、exceptions,所有模块均可访问
Python对于变量的查找顺序为:
- local namespace,当前函数或类方法,若找到,则停止搜索
- global namespace,当前模块,若找到,则停止搜索
- build-in namespace,最后查找build-in命名空间
- 若变量不是build-in的内置函数或变量,Python将报错NameError
闭包比较特殊:在local namespace找不到该变量,会查找目标是父函数的local namespace
看一个简单的示例:
print('这是一个自定义基本模块')
num = 1000
def fun_a():
print('fun_a', num)
def fun_b():
print('fun_b')
if __name__=='__main__':
fun_a()
print('执行了该文件')
from base_module import fun_a,num print(num) num = 5000 print(num) fun_a()

我们可以看到,改变了num值,调用模块中的方法的时候,还是方法的模块直接的命名空间的num。
各类文件与目录
- .pyo:优化编译后的文件
- .pyc:Python编译之后的文件
- .pyd:包含Python代码的库文件(Windows)
- .pyi:静态类型信息文件
- .so:Linux的动态链接库
- .dll:Windows的动态链接库
- __pycache__:用于存储编译Python源文件的字节码文件目录(sys.dont_write_bytecode开关控制)
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













