9. 导入
在应用程序练习的第 1 版中遇到的错误,促使我们更深入地探讨 [import] 语句的作用。

9.1. 脚本 [import_01]
[imported]脚本将被各种脚本(也称为模块)导入:

模块在被导入时会被执行。因此,当 [被导入的] 模块被导入时:
- 第 3 行的输出将发生;
- 第 5 行中的变量 x 将被赋值;
[main_01]脚本如下:
- 第 2 行:导入 [imported] 模块。这将导致其被执行:
- 将显示值 2;
- 变量 x 被创建,其值为 4;
- 第 4 行:使用了来自导入模块的变量 x;
在 PyCharm 中,报告了一个错误:
在 [1] 中,PyCharm 提示无法识别 [imported] 模块。从技术角度讲,这意味着包含 [imported] 模块的文件夹未被添加到 PyCharm 的 Python 路径中。Python 路径是指用于搜索导入模块的文件夹集合。 要解决此问题,只需将包含 [imported] 模块的文件夹设置为 [Root Sources] 文件夹,本例中即 [import/01] 文件夹:


完成此步骤后,[import/01] 文件夹将被添加到 PyCharm 的 Python 路径中,错误提示随之消失:

- 在 [1] 中,[01] 文件夹的颜色已发生变化;
- 在 [2-3] 中,已不再显示错误;
执行结果如下:
注释
- 第 2 行是执行导入模块的结果;
- 第 3 行显示了来自导入模块的变量 x 的值;
本示例的核心要点在于:导入的模块(或脚本)会被执行,这是一个重要的概念。
[main_02]脚本如下:
- 第 2 行使用了不同的导入语法 [from 模块 import 对象1, 对象2, …]。这里,我们导入变量 [imported.x]。使用这种语法,变量 x 成为 [main_02] 脚本中的变量。我们不再需要在其前面加上其模块 [imported];
- 第 4 行:我们打印来自 [main_02] 的变量 x;
执行结果如下:
[main_03] 脚本如下:
第 2 行中的 [import *] 语法表示我们将从被导入的模块中导入所有可见对象(变量、函数)。
结果如下:
[main_04] 脚本如下:
第 3 行表明我们可以从 imported 模块导入一个对象,并为其指定一个别名。在此,变量 [imported.x] 变成了变量 [main_04.y]。结果与之前相同。
9.2. 脚本 [import_02]

导入的模块 [module1.py] 内容如下:
导入的模块定义了一个函数,这是常见的情况。
脚本 [main_01] 如下所示:
- 第 2 行:导入该模块。它将被执行。此处不会显示任何内容;
- 第 4 行:执行从导入模块中调用的 [f1] 函数;
执行结果如下:
注意:为防止 PyCharm 在第 2 行导入时报错,您必须将包含 [module1] 的文件夹添加到 PyCharm 的 [根目录] 中:

在 [1] 中,放置在 [源代码根目录] 中的 [02] 文件夹会变为蓝色。请注意,此处报告的错误并不影响脚本的正常运行。 实际上,当执行 [main_0x] 脚本时,该脚本所在的文件夹会自动添加到 Python 路径中。因此,[module1] 会被找到。从现在起,当截图中某个文件夹显示为蓝色时,表示它已被放置在 PyCharm 的 [源代码根目录] 中。
[main_02] 脚本内容如下:
- 第 2 行从模块 [module1] 中导入函数 [f1];
- 第 4 行调用了函数 f1;
结果与 [main_01] 脚本的结果完全相同。
9.3. 脚本 [import_03]

注:[03] 位于项目的 [Root Sources] 目录下。
新脚本将导入 [module2] 模块,该模块并不位于与它们相同的文件夹中。
[module2] 脚本内容如下:
因此,该脚本定义了一个函数 [f2]。
[main_01]脚本如下:
- 第 2 行:我们使用一种特殊表示法来指示如何查找 [module2] 模块。[dir1.module2] 应理解为路径 [dir1/module2]:要查找 [module2],请从当前脚本所在的文件夹 [main_01] 开始,然后进入 [dir1],在那里你会找到 [module2]。 请注意,路径的起点是正在导入的脚本所在的文件夹;
- 第 4 行:执行 [module2] 中的函数 [f2];
结果如下:
第 2 行,[f2] 函数的结果。
[main_02]脚本如下:
在第 2 行,我们将模块 [dir1.module] 重命名,以简化第 4 行的代码。
[main_03]脚本如下:
这次,在第 2 行,我们只导入了 [f2] 函数,该函数随后成为 [main_03] 脚本中的一个函数(第 4 行)。
所有这些脚本在 PyCharm 环境中的运行效果与在 Python 控制台中一样好。原因是无论哪种情况,执行脚本所在的目录(此处为 [03] 目录)都是 Python 路径的一部分。因此,系统能够找到 [dir1/module2] 目录。
9.4. 脚本 [import_04]

在此,[dir1] 和 [dir2] 文件夹已被放置在 PyCharm 项目的 [Root Sources] 中。
首先导入的模块是 [module3]:
导入的第二个模块是 [module4]:
- 第 1 行:我们从 [module3] 导入函数 [f3]。这里,[module3] 是可见的,因为我们将其目录 [dir1] 放置在了 [Root Sources] 中;
- 第 4–6 行:我们定义了一个函数 [f4],该函数调用来自 [module3] 的函数 [f3];
主脚本 [main_01] 如下所示:
- 第 2 行:导入 [module4] 模块。这是可见的,因为我们将它的目录 [dir2] 放置在 PyCharm 的 [Root Sources] 中;
- 第 4 行:执行来自 [module4] 的 [f4] 函数;
在 PyCharm 中运行 [main_01] 的结果如下:
现在,让我们在 Python 终端(控制台)中运行 [main_01]:

结果如下:
发生了什么?Python 终端并不了解 PyCharm 的 Python 路径或 [源代码根目录]。它拥有自己的 Python 路径。在这个路径中,总是包含正在执行的脚本所在的文件夹,在本例中即 [main_01] 脚本。因此,它知道 [import/04] 文件夹。在执行的脚本中,它找到了这一行:
from module4 import f4
Python 解释器会在其 Python 路径中的文件夹内查找 [module4]。然而,[module4] 并不位于 [import/04] 文件夹中(尽管该文件夹确实在 Python 路径内),而是位于 [import/04/dir2] 文件夹中,而该文件夹不在 Python 路径内。因此导致了错误。
因此,我们遇到了一个曾遇到过的问题:在 PyCharm 中能正常运行的脚本,在 Python 终端中可能会崩溃。这是一个反复出现的问题,我们需要解决它。
9.5. 脚本 [import_05]

注意:[dir1] 和 [dir2] 文件夹已添加到 Python 路径中。请注意这里已经存在冲突:[module3] 和 [module4] 在 PyCharm 的 Python 路径中将出现在两个位置:
- 对于 [module3],分别位于 [import/04/dir1] 和 [import/05/dir1] 中;
- 位于 [import/04/dir2] 和 [import/05/dir2] 中的 [module4];
然后我们可以将 [import/04/dir1] 和 [import/04/dir2] 从 PyCharm 项目的 [根源文件] 中移除。 事实证明,此处的 [import/05/dir1] 是 [import/04/dir1] 的副本([dir2] 也是如此),因此不会出现问题。不过值得注意的是,在 PyCharm 内部,必须注意 [Root Sources] 中的文件夹列表,以避免冲突。
[main_01]脚本内容如下:
我们正在尝试解决 Python 路径问题。我们希望找到一种在 PyCharm 中和在 Python 终端中一样好用的方案。为此,我们将自行配置。
- 第 4–6 行:我们将目录 [., ./dir1, ./dir2] 添加到 Python 路径中。要使此操作生效,运行时的当前目录必须是 [import/05] 目录。在 PyCharm 中这通常成立,但在 Python 终端中则未必如此,我们稍后将看到;
- 第 8 行:我们导入 [module4]。根据刚才的操作,它应该位于 [./dir2] 目录中;
在 PyCharm 中执行将得到以下结果:
现在,在 Python 终端中:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\05>python main_01.py
f3
f4
第 1 行,执行目录为 [import/05]。
现在让我们从 [import/05] 目录向上移动一层:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\05>cd ..
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import>python 05/main_01.py
Traceback (most recent call last):
File "05/main_01.py", line 8, in <module>
from module4 import f4
ModuleNotFoundError: No module named 'module4'
- 第 2 行:当执行 [main_01] 时,我们已不在 [import/05] 文件夹中,而是在 [import] 文件夹中。然而,我们写的是:
sys.path.append(".")
sys.path.append("./dir1")
sys.path.append("./dir2")
这会将文件夹 [import, import/dir1, import/dir2] 添加到 Python 路径中,这完全不是我们想要的结果。请注意,将不存在的文件夹(import/dir1、import/dir2)添加到 Python 路径中并不会引发错误。
我们取得了一些进展,但这还不够。我们需要将绝对路径添加到 Python 路径中,而不是相对路径。
[main_02] 脚本是 [main_01] 的变体,它使用了一个配置文件 [config.json]:
{
"dependencies": [
"C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/import/05/dir1",
"C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/import/05/dir2"
]
}
[dependencies] 键的值是需要添加到 Python 路径中的目录列表。请注意,这里我们使用的是绝对路径,而不是相对路径。
[main_02] 脚本使用 [config.json] 文件的方式如下:
- 第 6 行:请注意,我们使用了配置文件的绝对路径;
- 第 8–9 行:读取配置文件。根据其内容构建一个字典 [config](第 9 行);
- 第 11–13 行:数组 [config['dependencies']] 的元素被添加到 Python 路径中。请注意,由于我们在 [config.json] 中使用了绝对文件夹名称,因此我们向 Python 路径中添加的也是绝对路径;
- 第 16 行:导入 [module4]。由于 [dir2] 现已加入 Python 路径,因此应能找到该模块;
执行结果与 [main_02] 相同,唯一区别在于即使执行目录不再是 [import/05],脚本仍会继续运行:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import>python 05/main_02.py
f3
f4
第 1 行,执行目录为 [import]。
我们取得了进展。我们已经看到:
- 我们必须自己构建 Python 路径;
- 我们必须包含应用程序所导入模块所在的所有文件夹的绝对路径;
然而,在脚本中写入绝对路径并不是解决办法。一旦项目被移至其他位置,它便无法正常运行。我们需要另寻他法。
9.6. 脚本 [import_06]

注意:文件夹 [06, dir1, dir2] 已放置在 PyCharm 项目的 [Root Sources] 目录下。文件夹 [dir1, dir2] 与前面的示例中的完全相同。
[config.json] 文件内容如下:
{
"rootDir": "C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06",
"relativeDependencies": [
"dir1",
"dir2"
],
"absoluteDependencies": [
]
}
我们将引入两种类型的路径:
- 绝对路径,第 7–8 行;
- 相对路径,第3–6行。这些路径相对于第2行的根目录。因此,当项目移动到新位置时,只需修改这一行;
[utils.py] 脚本使用 [config.json] 文件并构建 Python 路径:
- 第 8 行:[config_app] 函数将配置文件的名称作为参数接收;
- 第 12–14 行:使用配置文件创建 [config] 字典;
- 第 20 行:[sys.path] 是 Python 路径中的目录列表;
- 第 17–20 行:将配置文件中的相关依赖项添加到 Python 路径中。这些依赖项被添加到 [sys.path] 列表的开头(第 20 行)。这是因为当 Python 搜索模块时,会按顺序遍历 [sys.path] 中的目录。 然而,在本文档中,同名的模块将位于 [sys.path] 内的不同目录中。通过将应用程序的依赖项放置在 [sys.path] 数组的首位,我们确保在搜索 [sys.path] 中其他可能包含同名模块的目录之前,先搜索这些依赖项;
- 第 21–24 行:将配置文件的绝对依赖项添加到 Python 路径中;
- 第 26 行:返回应用程序配置;
- 第 29–30 行:[get_scriptdir] 函数返回包含当前正在运行的脚本(即包含该函数调用的脚本)的目录的绝对路径;
主脚本 [main] 如下所示:
- 第 4 行:导入了 [config_app] 函数。请注意,由于 [utils] 和 [main] 位于同一目录下,因此此 [import] 语句始终有效。这是因为主脚本所在的目录会被自动添加到 Python 路径中;
- 第 7–12 行:[display_path] 函数显示 Python 路径中的文件夹列表;
- 第 19 行:配置应用程序。请注意,配置文件的绝对路径被传递给了 [config_app] 函数。执行此语句后,Python 路径已被重建;
- 第 22 行:我们导入 [module4]。得益于 Python 路径的重建,该模块将被成功找到;
- 第 24 行:执行 [f4] 函数;
在 PyCharm 环境中,执行结果如下:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/import/06/main.py
avant....------------------------------
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\06
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\fonctions\shared
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v01\shared
…
C:\Program Files\Python38\python38.zip
C:\Program Files\Python38\DLLs
C:\Program Files\Python38\lib
C:\Program Files\Python38
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages
après....------------------------------
C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06/dir2
C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06/dir1
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\06
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\fonctions\shared
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v01\shared
….
C:\Program Files\Python38\python38.zip
C:\Program Files\Python38\DLLs
C:\Program Files\Python38\lib
C:\Program Files\Python38
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages
f3
f4
done
Process finished with exit code 0
注释
- 第 2–13 行:PyCharm 的 Python 路径。它包含放置在项目 [Root Sources] 中的所有文件夹;
- 第 14–29 行:由 [config_app] 函数构建的 Python 路径。第 15–16 行包含我们添加的两个依赖项;
- 第 22–27 行:执行该脚本的 Python 解释器的系统目录;
- 第 28–29 行:执行正常进行;
现在,让我们回到之前引发运行时错误的上下文:
- 打开一个 Python 终端;
- 切换到不包含该脚本的目录;
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import>python 06/main.py
avant....------------------------------
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\06
C:\Program Files\Python38\python38.zip
C:\Program Files\Python38\DLLs
C:\Program Files\Python38\lib
C:\Program Files\Python38
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages
après....------------------------------
C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06/dir2
C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06/dir1
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\06
C:\Program Files\Python38\python38.zip
C:\Program Files\Python38\DLLs
C:\Program Files\Python38\lib
C:\Program Files\Python38
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages
f3
f4
done
这次运行成功了(第20–21行)。请注意,[sys.path] 包含的目录与在 PyCharm 中运行时并不相同。
9.7. 脚本 [import_07]
我们通过以下两种方式改进了之前的解决方案:
- 我们将配置文件 [config.json] 替换为脚本 [config.py]。事实上,JSON 文件存在一个显著问题:无法添加注释。[config.json] 字典可以替换为 Python 字典,后者具有可添加注释的优势;
- 我们使用一个对机器上所有 Python 项目都可见的模块;
9.7.1. 安装全局模块

在上文中,我们在 PyCharm 项目中创建了一个名为 [packages/myutils] 的文件夹(名称不重要)。
[myutils.py] 脚本内容如下:
- 第 6–18 行:[set_syspath] 函数使用作为参数传递的目录列表创建一个 Python 路径;
- 第 12–15 行:我们检查要添加到 Python 路径中的目录是否存在;
[__init.py__] 脚本(名称前后各有一个下划线;这是必需的)如下:
from .myutils import set_syspath
我们从 [myutils] 脚本中导入 [set_syspath] 函数。表示法 [.myutils] 指代路径 [./myutils],这意味着 [myutils] 脚本位于与 [__init.py] 相同的目录下。 我们本可以使用 [myutils] 这种写法。但是,我们将创建一个全局 [myutils] 模块。因此,[from myutils import set_syspath] 这种写法会变得模棱两可。它是指从当前目录导入 [myutils] 脚本,还是指导入全局 [myutils] 脚本?[.myutils] 这种写法解决了这种歧义。
[setup.py] 脚本(此处的名称也是固定的)如下所示:
from setuptools import setup
setup(name='myutils',
version='0.1',
description='Utilitaire fixant le Python Path',
url='#',
author='st',
author_email='st@gmail.com',
license='MIT',
packages=['myutils'],
zip_safe=False)
在此脚本中,我们描述了即将创建的模块。这里我们将本地创建该模块。不过,创建官方发布的模块(参见 |pypi|)也采用相同的流程。此处的关键要点如下:
- 第 3 行:正在创建的模块名称;
- 第 4 行:模块的版本号;
- 第 5 行:模块的描述;
- 第 7–8 行:模块的作者;
若要以全机范围安装此模块,请按以下步骤操作:

然后,在 Python 终端中输入以下命令 [pip install .]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install .
Processing c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\packages
Using legacy setup.py install for myutils, since package 'wheel' is not installed.
Installing collected packages: myutils
Attempting uninstall: myutils
Found existing installation: myutils 0.1
Uninstalling myutils-0.1:
Successfully uninstalled myutils-0.1
Running setup.py install for myutils ... done
Successfully installed myutils-0.1
从现在起,该机器上的任何脚本都可以导入 [myutils] 模块,而无需将其包含在项目代码中。
9.7.2. [config.py] 脚本

[config.py] 脚本负责处理应用程序的配置:
- 第 1 行:[configure] 函数负责处理应用程序配置;
- 第 7–10 行:之前在 [config.json] 中的字典;
- 第 9–10 行:由于我们处于脚本中,可以直接访问绝对文件夹名称 [dir1, dir2];
- 第 12–14 行:我们使用刚刚创建的 [myutils] 模块中的 [set_syspath] 函数来设置配置的 Python 路径;
- 第 20 行:返回应用程序配置字典。此处,该字典为空;
9.7.3. [main.py] 脚本
主脚本 [main] 如下所示:
- 第 2–4 行:我们使用 [config.py] 模块配置应用程序。由于该模块与主脚本位于同一目录下,因此可以访问它。然而,主脚本所在的目录始终是 Python 路径的一部分;
- 当执行到第 6 行时,Python 路径已构建完成,并包含了 [module4] 模块所在的文件夹。因此,我们可以在第 7 行导入该模块;
- 第 10–15 行:剩下的就是调用 [f4] 函数;
在 PyCharm 中的执行结果如下:
在主脚本目录外的 Python 终端中,结果如下:
从现在起,我们将始终按照相同的步骤来配置应用程序:
- 在主脚本所在的目录中放置一个 [config.py] 脚本。该脚本包含一个 [configure] 函数,其作用有两点:
- 构建应用程序的 Python 路径。为此,[config.py] 会列出所有包含应用程序所用模块的文件夹,并使用这些模块的绝对路径来构建 Python 路径;
- 构建应用程序配置的 [config] 字典;
我们将这种方法应用到练习题的第二个版本中。您可能还记得,第一个版本在 PyCharm 环境中可以运行,但在 Python 终端中却无法运行。问题出在 Python 路径上。