像计算机科学家一样思考:用 Python 学习 第 2 版/函数
在编程的上下文中,函数是一系列命名的语句,用于执行所需的运算。此运算在函数定义中指定。在 Python 中,函数定义的语法如下:
您可以为创建的函数取任何您想要的名称,但不能使用 Python 关键字作为名称。参数列表指定为了使用新函数,您必须提供哪些信息(如果有)。
函数内部可以有任意数量的语句,但它们必须从def缩进。在本手册中的示例中,我们将使用四个空格的标准缩进。函数定义是我们将看到的几个复合语句中的第一个,它们都具有相同的模式
- 一个头部,以关键字开头,以冒号结尾。
- 一个主体,由一个或多个 Python 语句组成,每个语句都从头部缩进相同的量——4 个空格是 Python 标准。
在函数定义中,头部中的关键字是def,后面跟着函数名称和一个用括号括起来的参数列表。参数列表可以为空,也可以包含任意数量的参数。无论哪种情况,括号都是必需的。
我们将要编写的头几个函数没有参数,因此语法如下所示
此函数名为new_line。空括号表示它没有参数。它的主体仅包含一个语句,该语句输出一个换行符。(当您使用没有参数的print命令时,就会发生这种情况。)
定义新函数不会使函数运行。为此,我们需要一个函数调用。函数调用包含要执行的函数的名称,后跟一个值列表,称为实参,这些值被分配给函数定义中的参数。我们的第一个示例具有空参数列表,因此函数调用不接受任何实参。但是,请注意,函数调用中需要括号
此程序的输出为
两行之间的额外空格是new_line()函数调用导致的。如果我们想要在行之间添加更多空格怎么办?我们可以重复调用同一个函数
或者我们可以编写一个名为three_lines的新函数,它打印三个换行符
此函数包含三个语句,所有语句都缩进了四个空格。由于下一个语句没有缩进,因此 Python 知道它不是函数的一部分。
您应该注意此程序的一些内容
- 您可以重复调用相同的过程。事实上,这样做非常普遍且有用。
- 您可以让一个函数调用另一个函数;在本例中three_lines调用new_line.
到目前为止,创建所有这些新函数的价值可能还不清楚。实际上,有很多原因,但此示例演示了两个原因
- 创建新函数使您有机会命名一组语句。函数可以通过将复杂的计算隐藏在单个命令后面,以及使用英文单词代替神秘的代码来简化程序。
- 创建新函数可以通过消除重复代码来使程序更小。例如,打印九个连续换行符的简短方法是调用three_lines三次。
将上一节中的代码片段合并到名为tryme1.py的脚本中,整个程序如下所示
此程序包含两个函数定义new_line和three_lines。函数定义像其他语句一样被执行,但效果是创建新函数。函数内部的语句在函数被调用之前不会被执行,并且函数定义不会生成任何输出。
正如您可能预期的那样,您必须在执行函数之前创建它。换句话说,函数定义必须在第一次调用它之前执行。
为了确保函数在第一次使用之前已定义,您必须知道语句执行的顺序,这称为执行流程。
执行始终从程序的第一条语句开始。语句按顺序从上到下依次执行。
函数定义不会改变程序的执行流程,但请记住,函数内部的语句在函数被调用之前不会被执行。虽然不常见,但您可以在另一个函数内部定义一个函数。在这种情况下,内部定义在外部函数被调用之前不会被执行。
函数调用就像执行流程中的一个绕路。程序不会继续执行下一条语句,而是跳转到被调用函数的第一行,执行那里的所有语句,然后返回到它离开的地方继续执行。
这听起来很简单,直到您记得一个函数可以调用另一个函数。在执行一个函数的过程中,程序可能需要执行另一个函数中的语句。但在执行该新函数时,程序可能需要执行另一个函数!
幸运的是,Python 擅长跟踪其所在位置,因此每次函数完成时,程序都会在调用它的函数中从它离开的地方继续执行。当程序到达程序末尾时,它将终止。
这个故事的寓意是什么?当您阅读程序时,不要从上到下阅读。相反,请遵循执行流程。
大多数函数需要实参,这些值控制函数的工作方式。例如,如果您想找到数字的绝对值,则必须指出该数字是什么。Python 具有一个用于计算绝对值的内置函数
在此示例中,abs函数的实参为 5 和 -5。
有些函数接受多个实参。例如,内置函数pow接受两个实参,底数和指数。在函数内部,传递的值会被分配给称为参数的变量。
另一个接受多个实参的内置函数是max.
max可以发送任意数量的实参(用逗号分隔),并返回发送的最大值。实参可以是简单值或表达式。在最后一个示例中,返回 503,因为它大于 33、125 和 1。
这是一个具有参数的用户定义函数的示例
此函数接受一个实参并将其分配给名为param的参数。参数的值(此时我们不知道它将是什么)被打印两次,然后换行。名称param是为了强化它是参数的概念而选择的,但通常,您需要选择一个描述参数在函数中用法的名称。
交互式 Python shell 为我们提供了一种方便的方式来测试我们的函数。我们可以使用import 语句将我们在脚本中定义的函数引入解释器会话。要了解其工作原理,假设print_twice函数在名为chap03.py的脚本中定义。我们现在可以通过将其导入到我们的 Python shell 会话中来交互地测试它
在函数调用中,实参的值被分配给函数定义中相应的参数。实际上,就好像param = 'Spam'在print_twice('Spam')被调用时执行,param = 5在print_twice(5)中执行,以及param = 3.14159在print_twice(3.14159).
中执行。任何可以打印的实参类型都可以发送到print_twice在第一个函数调用中,实参是一个字符串。在第二个函数调用中,它是一个整数。在第三个函数调用中,它是一个float.
与内置函数一样,我们可以使用表达式作为print_twice:
'Spam'*4的实参,首先计算结果为'SpamSpamSpamSpam',然后将其作为实参传递给
print_twice.就像数学函数一样,Python 函数可以被组合,这意味着你可以使用一个函数的结果作为另一个函数的输入。
在第一个例子中,abs(-7)计算结果为 7,然后成为的参数。print_twice在第二个例子中,我们有两层组合,因为abs(-11)首先计算结果为 11,然后max(3, 1, 11, 7)计算结果为 11,并且print_twice(11)然后显示结果。
我们也可以使用变量作为参数
请注意这里非常重要的一点。我们作为参数传递的变量名(sval)与参数名(param)无关。同样,就好像param = sval在print_twice(sval)被调用一样。无论在调用者中该值被命名为何,在print_twice中,它的名称是param.
在函数内部创建局部变量时,它只存在于函数内部,你无法在函数外部使用它。例如
此函数接受两个参数,将它们连接起来,然后打印两次结果。我们可以用两个字符串调用该函数
当cat_twice终止时,变量cat被销毁。如果我们尝试打印它,我们会得到一个错误
参数也是局部的。例如,在函数外部print_twice,不存在param这样的东西。如果你尝试使用它,Python 会报错。
为了跟踪哪些变量可以在哪里使用,有时绘制栈图会很有用。与状态图类似,栈图显示每个变量的值,但它们还显示每个变量所属的函数。
每个函数都由一个框架表示。框架是一个带有函数名称的框,函数的参数和变量在框内。前面示例的栈图如下所示
栈图栈的顺序显示了执行流程。print_twice被cat_twice中执行,以及cat_twice被__main__调用,这是最顶层函数的特殊名称。当你在任何函数之外创建变量时,它属于__main__.
每个参数都引用与其对应的参数相同的值。所以,part1与chant1, part2与chant2中执行,以及param与cat.
如果在函数调用期间发生错误,Python 会打印函数的名称,以及调用它的函数的名称,以及调用该函数的函数的名称,一直回到最顶层的函数。
要查看它是如何工作的,请创建一个名为tryme2.py的 Python 脚本,如下所示
我们添加了语句,print cat在print_twice函数中,但是cat在此处未定义。运行此脚本将产生如下错误消息
此函数列表称为回溯。它会告诉你错误发生在哪个程序文件中,哪一行,以及当时正在执行哪些函数。它还会显示导致错误的代码行。
请注意回溯和栈图之间的相似性。这并非巧合。事实上,回溯的另一个常用名称是堆栈跟踪。
- 使用文本编辑器,创建一个名为tryme3.py在此文件中编写一个名为nine_lines的函数,该函数使用three_lines打印九行空白行。现在添加一个名为clear_screen的函数,该函数打印 25 行空白行。程序的最后一行应该是对clear_screen.
- 将tryme3.py的最后一行移动到程序的顶部,以便对clear_screen的函数调用出现在函数定义之前。运行程序并记录您收到的错误消息。您能否陈述关于函数定义和函数调用的规则,该规则描述了它们在程序中可以相对于彼此出现的位置?
- 从tryme3.py的工作版本开始,将new_line的定义移动到three_lines的定义之后。记录运行此程序时发生的情况。现在将new_line的定义移动到对three_lines()的调用的下方。解释这如何成为你在上一道练习中陈述的规则的一个例子。
填写cat_n_times的函数定义的主体,以便它打印字符串 s n 次
将此函数保存在名为import_test.py现在在 Unix 提示符下,确保你在import_test.py所在的同一目录中(ls应该显示import_test.py)。启动 Python shell 并尝试以下操作
如果一切正常,你的会话应该与这个会话一样。尝试对cat_n_times进行其他调用,直到你对它的工作原理感到满意。