6. 函数

6.1. 脚本 [fonc_01]:变量作用域
脚本 [fonc_01] 展示了函数之间变量作用域的示例:
结果
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/fonctions/fonc_01.py
f1[i,j]=[1,10]
f2[i,j]=[2,20]
f3[i,j]=[1,30]
[i,j]=[2,0]
Process finished with exit code 0
注:
- 该脚本演示了变量 i 的用法,该变量在函数 f1 和 f2 中被声明为全局变量。在此情况下,主程序与函数 f1 和 f2 共享同一个变量 i。
6.2. 脚本 [fonc_02]:变量作用域
脚本 [fonc_03] 在脚本 [fonc_02] 的基础上,演示了如何避免使用全局变量:
注释:
- 第 2 行和第 12 行:变量 [i] 并未被声明为全局变量,而是作为参数传递给了函数 f1 和 f2;
- 第 9 行和第 19 行:函数 f1 和 f2 将修改后的变量 [i] 返回给主程序。主程序在第 36 行和第 37 行中获取该变量;
结果
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/fonctions/fonc_02.py
f1[i,j]=[1,10]
f2[i,j]=[2,20]
f3[i,j]=[1,30]
[i,j]=[2,0]
Process finished with exit code 0
6.3. 脚本 [fonc_03]:变量的作用域
脚本 [fonc_03] 演示了变量在函数内部和调用代码中使用的特殊性,这取决于该变量是否仅在函数内部用于读取。
注释
- 第 34 行:主代码定义了一个变量 [i];
- 第 1–5 行:函数 f1 同样使用了变量 [i],但未对其赋值。这是对变量 [i] 的读取。在此情况下,所使用的变量 [i] 来自调用代码中的第 34 行;
- 第 8–18 行:函数 f2 同样使用变量 [i],但在第 16 行对其赋值。在 f2 中对变量 [i] 赋值会自动使 [i] 成为函数 [f2] 的局部变量。因此,该函数将变量 [i] “隐藏” 起来,使其对调用代码不可见;
- 第 14 行:对局部变量 [i] 的写入操作将失败,因为当执行到第 14 行时该变量尚未赋值。它将在第 16 行获得其值。此时将引发异常。因此,第 14 行被置于 try/catch 代码块中;
- 第 21–29 行:f3 函数与 f2 函数功能相同,但更早定义了其局部变量 [i];
结果
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/fonctions/fonc_03.py
[f1] i=10
[f1] j=20
[main] i=10, j=20
[f2] erreur=local variable 'i' referenced before assignment
[main] i=10
[f3] i=7
[main] i=10
Process finished with exit code 0
6.4. 脚本 [fonc_04]:参数传递模式
脚本如下:
结果
注:
- 在 Python 中,一切都是对象。有些对象被称为“不可变”:它们无法被修改。数字、字符串和元组就属于这种情况。当 Python 对象作为参数传递给函数时,传递的是对象的引用,除非这些对象是“不可变”的,在这种情况下,传递的是对象的值;
- 函数 f1(第 2 行)和 f2(第 7 行)旨在演示输出参数的传递。我们希望函数的实际参数能被该函数修改;
- 第 2–3 行:函数 f1 修改了其形式参数 a。我们想知道实际参数是否也会被修改;
- 第 14–15 行:实际参数为 x = 1。结果的第 2 行显示实际参数未被修改。因此,实际参数 x 和形式参数 a 是两个不同的对象;
- 第 8–10 行:函数 f2 修改其形式参数 a 和 b,并将它们作为结果返回;
- 第 17–18 行:将实际参数 (x, y) 传递给 f2,并将 f2 的结果赋值给 (x, y)。结果的第 3 行表明实际参数 (x, y) 已被修改。
由此可得,当“不可变”对象作为输出参数时,它们必须是函数返回结果的一部分。
6.5. 脚本 [fonc_05]:脚本中函数的顺序
脚本 [fonc_05] 表明,如果函数在代码中尚未出现过,则无法被调用:
注释
- 第 2 行会引发错误,因为它使用了函数 f2,而该函数在脚本中尚未定义;
结果
6.6. 脚本 [fonc_06]:脚本中函数的执行顺序
脚本 [fonc_06] 表明,适用于调用代码的规则并不适用于函数:
注释
- 第 3 行:函数 [f2] 使用了脚本后文定义的函数 [f1]。但这不会引发错误。因此我们可以得出结论:在 Python 脚本中,函数的定义顺序并不重要;
结果
6.7. 脚本 [fonc_07]:使用模块
脚本 [fonc_07] 演示了如何将函数封装在模块中。

我们将可重用的函数封装在模块中,而不是将它们从一个脚本移动到另一个脚本:
- 而是将它们放在一个单独的文件中,并以特定方式进行声明;
- 需要这些函数的脚本会“导入”包含这些函数的模块;
脚本 [fonctions_module_01] 如下所示:
有几种方法可以确保 [fonctions_module_01] 脚本中的函数能够被其他脚本调用。这些方法因脚本是否在 [PyCharm] 中运行而有所不同。
在 [PyCharm] 中,导入的模块会在名为 [Root Sources] 的特定文件夹中进行搜索。有两种方法可以将文件夹设为 [Root Source]:

- 在 [4] 中,该文件夹的颜色已发生变化;
完成此操作后,[functions/modules] 文件夹将被识别为源文件夹。随后您可以在脚本中编写:
以此导入并使用在 [functions_module_01.py] 模块中定义的函数 f2。
![]() |
另一种方法是使用项目属性:
- 在上文中,序列 [1-6] 允许将 [shared] 文件夹用作存储待导入模块的文件夹;
目前,除了项目根目录之外,我们不会将任何文件夹声明为 [Sources Root]:

完成上述设置后,我们可以编写以下 [fonc-07] 脚本:
- 第 2 行:我们导入 [sys] 对象,以便在第 5 行使用其 [path] 属性,该属性提供所谓的 [Python Path]:一个用于搜索导入模块的目录列表;
- 第 6 行:我们从 [fonctions_module_01] 模块导入 f2 函数。要引用该模块,我们使用从项目根目录到该模块的路径。在 PyCharm 中,当在脚本中查找导入的模块时,项目根目录始终包含在搜索的文件夹中。因此,该文件夹是项目 [Python Path] 的一部分。这正是第 5 行将帮助我们验证的内容;
- 第 6 行:若要描述从项目根目录到 [fonctions_module_01] 文件夹的路径,我们会写成 [fonctions/shared/fonctions_module_01]。在模块路径中,斜杠 (/) 会被替换为点 (.)。因此我们写成 [fonctions.modules.fonctions_module_01];
- 第 6 行之后定义了函数 f2。我们在第 8 行中使用了它;
结果
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/fonctions/fonc_07.py
Python path=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', '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:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\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']
310
Process finished with exit code 0
上文:
- 绿色高亮部分显示,项目根目录是 [Python Path] 的一部分;
- 黄色高亮部分显示,包含已执行脚本的文件夹也是 [Python Path] 的一部分;
- [Python Path] 的其他元素则直接来自 Python 安装目录;
如果我们不使用 PyCharm 来运行 [fonc-07],会发生什么?


- 在 [1] 中,我们运行 [fonc-07] 脚本。当前所在位置为 [functions] 文件夹;
- 在 [2] 中,我们可以看到执行目录是 [Python Path] 的一部分。这种情况总是成立的。我们还可以看到,根目录 [C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020] 并不属于 [Python Path];
- 在 [3] 中,Python 解释器报告无法找到 [fonctions] 模块;
为了查找被导入的模块 [fonctions.shared.fonctions_module_01],Python 解释器会在 [Python Path] 中的文件夹内搜索名为 [fonctions] 的子文件夹。但无论在何处都找不到该文件夹。 这是因为 [fonctions] 子文件夹位于 [C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020] 文件夹下,而该文件夹不属于 [Python Path]。
[fonc-08] 脚本为该问题提供了一种可能的解决方案。
6.8. 脚本 [fonc_08]:向 [Python Path] 添加文件夹
可以通过编程方式修改 [Python Path],具体方法如 [fonc-08] 脚本所示:
注释
- 第 4 行:特殊变量 [__file__] 表示正在执行的脚本名称。根据执行上下文的不同,该名称可能是绝对路径(PyCharm)或相对路径(控制台)。[os.path.abspath] 函数返回传入名称对应的文件的绝对路径。[os.path.dirname] 函数返回传入名称对应的文件所在目录的绝对路径;
- 第 10 行:[sys.path] 是一个列表,其中包含在查找模块时需要搜索的目录名称。我们将第 4 行定义的项目根目录添加到该列表中;
- 我们分别显示修改前(第 8 行)和修改后(第 12 行)的 [Python Path];
- 第 15 行:我们导入 [fonctions_module_01] 模块,该模块包含 f2 函数;
在 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/fonctions/fonc_08.py
Python path avant=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', '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:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\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']
Python path après=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', '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:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\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', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions/shared']
310
Process finished with exit code 0
- 第 3 行:我们可以看到 [shared] 文件夹在 [Python Path] 中出现了两次。虽然可以避免这种情况,但在此处不会造成任何问题;
- 第 4 行:f2 函数已成功执行;
现在让我们在终端中运行 [fonc-08]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\fonctions>python fonc_08.py
Python path avant=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\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']
Python path après=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\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', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions/shared']
310
- 第 2 行:与之前一样,[shared] 文件夹不在 [Python Path] 中;
- 第 3 行:现在已包含;
- 第 4 行:找到了函数 f2;
6.9. 脚本 [fonc_09]:声明参数类型
[fonc_09] 脚本表明,您可以声明函数参数的类型以及返回值的类型。不过,这种声明仅用于记录函数信息。Python 解释器不会检查实际函数参数是否为预期类型。但 PyCharm 会标记实际参数与形式参数之间的类型不一致。仅凭这一点,类型声明就变得不可或缺。
脚本内容如下:
注:
- 第 5 行:我们声明形式参数 [param] 的类型为 [int],且函数的返回类型也是 [int];
- 第 11 行:函数 [show] 的实际参数类型正确;
- 第 12 行:函数 [show] 的实际参数类型不正确;
结果
- 第 10 行:参数 [param] 的类型是 [str]。当此消息出现时,我们已经进入了函数 [show] 的代码。因此,Python 解释器已接受函数 [show] 的实际参数类型为 [str];
- 代码的第 7 行触发了结果中第 4–10 行所示的异常;
尽管如此,PyCharm 仍提示存在问题:

在 [1] 中,PyCharm 已将错误的调用标记出来。
6.10. 脚本 [fonc_10]:命名参数
要向函数传递参数,可以使用其形式参数的名称。在这种情况下,无需遵循形式参数的顺序:
注释
- 第 2 行:函数 f 有两个形式参数,x 和 y;
- 第 7 行:调用函数 f 时,可以使用形式参数的名称。这种做法至少在以下两种情况下很有用:
- 函数参数较多,且其中大部分具有默认值。调用函数时,上述方法允许您仅对不希望使用默认值的参数进行初始化;
- 如果形式参数具有有意义的名称,那么在函数调用中使用命名参数可以提高代码的可读性;
结果
6.11. 脚本 [fonc_11]:递归函数
脚本 [fonc_11] 是一个递归函数(即调用自身的函数)的示例:
注释
- 第 1-9 行:阶乘函数;
- 第 9 行:[阶乘] 函数调用自身;
- 第5-6行:递归函数必须在满足某个条件时终止;否则,就会发生无限递归;
结果
6.12. 脚本 [fonc_12]:递归函数
[fonc_12] 函数进一步详细说明了递归的工作原理:
注释
- 第 5 行:我们仍然关注阶乘函数。我们向其中添加了参数 [j];
- 第12行:变量j在每次阶乘计算后都会递增。我们在递归(第15行)之前(第12行)和之后(第16行)显示其值;
结果
- 第2–8行:我们可以看到,只要递归仍在进行,[j]的值就会增加,直到满足递归停止的条件。从那一刻起,[fact]函数的调用返回将按与调用相反的顺序进行;
- 第 10–16 行:这些输出反映了阶乘调用中连续的返回过程。变量 [j] 恢复到其初始值 1;
