Python 中包与模块的 import 机制详解

Miles
Miles
发布时间:2026-10-13| 3人浏览过
原创

本文深入解析 Python 中 import 语句的工作机制,涵盖包与模块的创建、命名空间隔离原理及多种导入方式。重点讲解直接导入包时子模块不自动加载的原因,以及通过 __all__ 控制 'from...import *' 行为的最佳实践,帮助开发者避免 ModuleNotFoundError 并优化代码结构。

Python import 机制实例解析

我们通过具体实例来解析 import 的实际作用。

首先构建如下包目录结构:创建一个名为 cookFish/ 的文件夹,其中包含两个文件, __init__.py和cookBook.py。

这样命名的原因是:假设我们要用 Python 处理与鱼相关的复杂烹饪任务,因此创建一个名为 cookFish 的包。作为包,其目录下必须包含 __init__.py 文件。此外,因为烧鱼需要菜谱,我们创建了 cookBook.py 文件。对于本次演示,这两个文件已足够,无需创建其他内容。


cookFish/
 __init__.py
 cookBook.py

在 cookFish/__init__.py 文件中编写如下代码:


__version__ = '0.1'
__author__ = 'XIE Byron'
def cookFish_hello():
 print("cookFish_Hello() from cookFish/__init__.py")

在 cookFish/cookBook.py 文件中编写如下代码:


def cookBook_hello():
 print("cookBook_hello() from cookBook.py")

注意:以下示例均在 Python 自带的命令行解释器(Windows 环境,Python 3.7)中执行。若使用 Jupyter Notebook 等其他环境,输出结果可能有所不同。

import 包的工作流程与命名空间

执行 'import package-name' 时究竟发生了什么?

以导入包 cookFish 为例。

>>> import cookFish

注意:

若导入时出现 ModuleNotFoundError 错误,示例如下:


>>> import cookFish
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'cookFish'

建议将 Python 的当前工作目录设置为 cookFish 所在父文件夹(即包含 cookFish 文件夹的上级目录)。具体命令如下:


>>> import os
>>> os.chdir(r'path\to\parent\folder\of\cookFish')

使用 dir 命令查看当前命名空间及 cookFish 命名空间下的内容。

>>> dir() # 查看当前命名空间下的对象。注意:此时 cookFish 已存在于当前命名空间中。


['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cookFish', 'os']

>>> dir(cookFish) # 查看 cookFish 命名空间下的对象。


['__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'cookFish_hello']

其中的 __author__, __version__, cookFish_hello 是我们定义的,它们都被导入到了 cookFish 的命名空间下。然而,cookFish 下的模块 cookBook.py 并未被导入。这是因为直接执行 import cookFish 仅运行 cookFish 文件夹下的 __init__.py 文件,而不会自动运行其他模块,因此 cookBook 未被导入。

注意:Python 中后缀为 .py 的文件称为模块(或脚本);包含 __init__.py 文件的文件夹称为包,通常还包含其他模块。

包与模块的命名空间隔离原理

包与模块的命名空间机制

这里阐述我对“在 cookFish 的命名空间下”这一概念的理解。

Python 执行 import A 时,会运行 A 对应的 Python 代码,并将运行结果存储在名为 A 的命名空间中。

注意:若 A 是包,其代码对应文件夹 A 下的 __init__.py 内容;若 A 是模块,则对应文件 A.py 的内容。

import B 会运行 B 的 Python 代码,并将结果存入名为 B 的命名空间。假设 A 和 B 中均有名为 X 的对象,则 A 中的 X 在当前命名空间中记为 A.X,B 中的 X 记为 B.X,两者在当前命名空间中不会重名。

注意:这里的“对象”指 Python 中的变量/属性、函数、类、实例等。

例如 __version__ 属性(或变量)位于 cookFish 的命名空间下,必须通过 cookFish.__version__ 才能访问。直接输入 __version__ 无法访问,会报错。

直接输入 __version__ 运行将报如下错误:


>>> __version__
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
NameError: name '__version__' is not defined

多种导入方式与别名使用

其他导入包/模块的方式

若想导入 cookFish 下的模块 cookBook,可使用以下语法:


>>> import cookFish.cookBook

此时,cookFish 的命名空间中新增了 cookBook。


>>> dir(cookFish)
['__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'cookBook', 'cookFish_hello']

随后即可通过全名 cookFish.cookBook 访问 cookBook.py 中的对象,例如:


>>> cookFish.cookBook.cookBook_hello()
cookBook_hello() from cookBook.py

名称过长,能否简化?当然可以:


>>> import cookFish.cookBook as cb

此时当前命名空间中新增对象 cb:


>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cb', 'cookFish', 'os']

随后即可通过别名 cb 访问 cookBook.py 中的对象,例如:


>>> cb.cookBook_hello()
cookBook_hello() from cookBook.py

能否仅将cookBook_hello()导入当前命名空间?当然可以。


>>> from cookFish.cookBook import cookBook_hello

此时 cookBook_hello 被导入到当前命名空间:


>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cb', 'cookBook_hello', 'cookFish', 'os']

随后即可直接访问 cookBook_hello(),无需任何前缀:


>>> cookBook_hello()
cookBook_hello() from cookBook.py

from...import * 与 __all__ 的控制机制

‘from 包/模块名 import *’ 是否导入所有对象?

能否一次性导入 cookFish 下的所有模块和包?答案是可以,也可以不是。

Python 有一条相关指令:

from 包/模块名 import *

例如 from cookFish import *,直观感觉是遍历 cookFish 下所有文件,找到所有子包和模块并全部导入当前命名空间。

然而,该操作在 Windows 和 Mac 系统上无法完美实现。因为这些系统的文件系统无法提供准确的文件名大小写信息。在这两个平台上,Python 无法确定应将 ECHO.py 导入为模块 echo、Echo 还是 ECHO 等(例如 Windows 95 上所有文件名首字母均显示为大写)。若 Python 将 ECHO.py 导入为模块 Echo,但实际代码中按 echo 使用,则会报错。[1]

Python 支持大小写敏感,Echo 和 ECHO 是两个不同的对象。

Python 的唯一解决方案是包作者提供明确的包索引,告知 Python 在代码中如何命名该模块。import 语句遵循以下约定:若在包的 __init__.py 中定义了 __all__ 列表,执行 from xxx import * 时,Python 仅导入 __all__ 列表中的对象。

注意:

__all__ 仅影响 from xxx import *,对其他 import 操作无影响。

在 cookFish/__init__.py 中,我们将函数 cookFish_hello 加入 __all__,代码如下:


__all__ = ['cookFish_hello', ] # added to support `from xxx import *`
__version__ = '0.1'
__author__ = 'XIE Byron'

def cookFish_hello():
  print("cookFish_Hello() from cookFish/__init__.py")

重启 Python 解释器,在导入前运行 dir() 显示当前命名空间对象。


>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'os']

注意:

为提高运行效率,Python 解释器对同一模块仅导入一次。模块被导入后,再次执行导入命令不会重新加载。为展示 from xx import * 的特殊性,需重启 Python 解释器(即关闭后重新进入)。

随后执行如下操作:


>>> from cookFish import *

输入 dir() 查看 cookFish_Hello() 是否被导入当前命名空间。


>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cookFish_hello', 'os']

可见仅有 __all__ 列表中的 cookFish_hello 被导入当前命名空间,其他内容均未导入,甚至 cookFish 本身也未导入。

因此,“能否一次性导入 cookFish 下的所有模块、所有包?”的答案是:能否一次导入,取决于包作者是否将所有子模块/子包加入 __all__ 列表。

参考

[1] Built-in Package Support in Python 1.5

版本

[1] version 1.0, released on 2019-04-21

[2] version 1.1, released on 2019-04-21

添加了 Python 命令的输出。运行工具为 Windows 版本 Python(3.7) 自带的命令行解释器。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。