目录
2.2.2 包的模块如何导入同个包中的其他模块(以dir2包为例)
2.2.3 包的模块如何导入其他包中的模块(以dir1包为例)
2.2.4 关于包的模块如何导入最外层的模块(以dir1包为例)
如果你还经常遇到 "ModuleNotFoundError: No module named" 这样的问题,那么可以稍微花几分钟看下这篇文章。
1 了解Python中的模块与包
模块module:其实就是一个个 python 文件。是一个包含所有你定义的函数和变量的文件,其后缀名是.py。模块可以被别的程序引入,以使用该模块中的函数等功能。这也是使用 python 标准库的方法。
包package:其实就是一个个目录,是一种 python 文件的代码组织。是一种用“点式模块名”构造 python 模块命名空间的方法。
举例说明:
下面所列的所有.py文件都是模块;main.py是这个Python工程的执行入口文件。
dir1跟dir2两个目录就是包。
|-- engzegngi/
| |-- dir1/
| | |-- __init__.py
| | |-- dir1_file1.py
| |
| |-- dir2/
| | |-- __init__.py
| | |-- dir2_file1.py
| | |-- dir2_file2.py
| |
| |-- main.py
2 如何正确引入模块
2.1 模块搜索路径
我们最常用的可能就是引入系统自带的模块,比如 import os 。Python是如何去搜索这些模块呢?那就要明白其“模块搜索路径”。官方定义如下图。
举例说明:
>>> import sys
>>> print(sys.path)
['',
'/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python38.zip',
'/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8',
'/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/lib-dynload',
'/Users/zeyiweng/.local/share/virtualenvs/gcloudsdk_autotest-wL5os1_R/lib/python3.8/site-packages']
其中,
PYTHONPATH:就是打印出来的第二个到第四个路径。
依赖于安装的默认值:就是打印出来的最后一个路径。(题外话:如果新建python虚拟环境,其实就是新建了个site-packages目录,然后依赖库都是安装到这个目录下,以此达到环境隔离的目录。)
2.2 实践说明
以下述结构的工程为例(注意跟main.py同目录下有个config.py)。
|-- engzegngi/
| |-- dir1/
| | |-- __init__.py
| | |-- dir1_file1.py
| |
| |-- dir2/
| | |-- __init__.py
| | |-- dir2_file1.py
| | |-- dir2_file2.py
| |
| |-- config.py
| |-- main.py
2.2.1 main.py如何导入其他模块
# main.py的所在目录为工程主目录engzegngi/
import config # 从工程主目录engzegngi/下就可以搜索到
import dir1.dir1_file1 # 从工程主目录engzegngi/下就可以搜索到
2.2.2 包的模块如何导入同个包中的其他模块(以dir2包为例)
"""
当前在dir2_file1.py中,如何引用同个包内的dir2_file2.py
"""
# 正确示范
import dir2.dir2_file2 # 从工程主目录engzegngi/下就可以搜索到
from . import dir2_file2 # 相对导入,从当前包的路径engzegngi/dir2中导入
# 错误示范
# 报错ModuleNotFoundError: No module named 'dir2_file2'
# 因为从工程主目录engzegngi/下没办法搜索到
import dir2_file2
2.2.3 包的模块如何导入其他包中的模块(以dir1包为例)
"""
当前在dir1_file1.py中,如何引用dir2包内的dir2_file2.py
"""
import dir2.dir2_file2 # 从工程主目录engzegngi/下就可以搜索到
# 错误示范
# 报错ImportError: attempted relative import beyond top-level package
# 这里特别注意。容易以为可以使用相对导入。实际上不允许的。
# 因为dir1、dir2两个目录已经在顶层了。
from ..dir2 import dir2_file2
这里可以查看官方文档链接中的“6.4.2. 子包参考”这一部分:6. 模块 — Python 3.10.5 文档
相对路径导入是在包中的子包去导入兄弟包的模块的一种方法!对于已经都在工程最外层的包,比如这里的dir1、dir2,就不能使用相对导入了,因为这两个包不是兄弟包。
如果dir1中有两个目录dir3、dir4,那么这两者就可以使用相对路径导入其他包中的模块。有兴趣的朋友可以自己试下。
这里没搞清楚的话,是很容易犯错的。常见的报错有:
"""报错举例"""
ImportError: attempted relative import with no known parent package
ImportError: attempted relative import beyond top-level package
ValueError: attempted relative import beyond top-level package
ImportError: attempted relative import with no known parent package
2.2.4 关于包的模块如何导入最外层的模块(以dir1包为例)
import config # 从工程主目录engzegngi/下就可以搜索到
2.3 其他
- 模块可以区分不同模块之间的全局变量名称。在模块内部,通过全局变量 `__name__` 可以获取模块名(即字符串)。当以脚本方式运行某模块时,`__name__` 赋值为 `"__main__"`。
- `from fibo import *` 会导入所有不以下划线( `_` )开头的名称。大多数情况下,不要用这个功能,这种方式向解释器导入了一批未知的名称,可能会覆盖已经定义的名称。`import fibo as fib` 模块名后使用 `as` 时,直接把 `as` 后的名称与导入模块绑定。
- 从包中导入模块,例如`import sound.effects.echo`。一般不建议整个包导入。这项操作花费的时间较长,需要用哪些部分,就写清楚导入就好。
3 参考文档
1. https://docs.python.org/zh-cn/3/tutorial/modules.html
2. https://python3-cookbook.readthedocs.io/zh_CN/latest/chapters/p10_modules_and_packages.html