持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情
python 操作可变参数
Python有许多内置函数,这些函数对可变数量的参数进行操作。例如
都是合法的(并评估你认为他们做了什么)。Python使程序员可以轻松定义自己的函数,这些函数接受可变数量的参数。解压缩运算符允许函数接受可变数量的位置参数。例如
打印 1.5 -1.0。请注意,参数列表中 * 后面的名称不必是 args。它可以是任何名称。对于平均值,编写 def 平均值(*数字)可能更具描述性。
范围界定
让我们看另一个小例子:
运行时,此代码打印
这是怎么回事?在调用 f 时,形式参数 x 在 f 的函数体上下文中本地绑定到实际参数 x 的值。尽管实际参数和形式参数具有相同的名称,但它们不是相同的变量。每个函数定义一个新的名称空间,也称为作用域。f 中使用的正式参数 x 和局部变量 y 仅存在于 f 定义的范围内。函数体中的赋值语句 x = x + y 将本地名称 x 绑定到对象 4。f 中的赋值对存在于 f 范围之外的名称 x 和 y 的绑定没有影响。
以下是考虑这个问题的一种方法:
-
在顶层,即 shell 的级别,符号表跟踪在该级别定义的所有名称及其当前绑定。
-
调用函数时,将创建一个新的符号表(通常称为堆栈帧)。此表跟踪函数中定义的所有名称(包括形式参数)及其当前绑定。如果从函数体内部调用函数,则会创建另一个堆栈帧。
3.当功能完成时,其堆栈帧消失。
在Python中,您始终可以通过查看程序文本来确定名称的范围。这称为静态或词汇范围。图 4-5 包含一个示例,说明了 Python 的范围规则。与代码关联的堆栈帧的历史记录如图 4-6 所示。
图 4-6 中的第一列包含函数体 f 之外已知的名称集,即变量 x 和 z,以及函数名称 f。第一个赋值语句将 x 绑定到 3。
值语句 z = f (x) 首先通过调用函数 f(具有 x 所绑定的值)来计算表达式 f(x)。输入 f 时,将创建一个堆栈帧,如第 2 列所示。堆栈帧中的名称是 x(形式参数 x,而不是调用上下文中的 x)、g 和 h。变量 g 和 h 绑定到函数类型的对象。这些函数的属性由 f 中的函数定义给出。
当从 f 内部调用 h 时,将创建另一个堆栈帧,如第 3 列所示。此帧仅包含局部变量 z。为什么它不包含 x?仅当名称是函数的正式参数或绑定到函数主体内对象的变量时,才会将该名称添加到与函数关联的作用域中。在 h 的正文中,x 仅出现在赋值语句的右侧。未绑定到函数体中任何位置(在本例中为 h 的主体)的名称(在本例中为 x)的出现会导致解释器搜索与定义函数的作用域关联的堆栈帧(与 f 关联的堆栈帧)。如果找到该名称(在本例中为该名称),则使用绑定到的值 (4)。如果在那里找不到它,则会生成一条错误消息。
当 h 返回时,与 h 的调用关联的堆栈帧将消失(它从堆栈顶部弹出),如第 4 列所示。请注意,我们从不从堆栈,但只有最近添加的帧。由于这种“最后进先出”(LIFO)行为,我们将其称为堆栈。(想象一下,煮一堆煎饼。当第一个从平底锅上下来时,厨师将其放在上菜的盘子上。当每个连续的煎饼从平底锅上下来时,它被堆叠在已经放在盘子上的煎饼上。当要吃煎饼时,第一个提供的煎饼将是堆叠顶部的煎饼,最后一个添加的煎饼 - 留下倒数第二个煎饼作为新的顶部煎饼添加到堆栈中,以及下一个要上菜的煎饼。
回到我们的Python示例,现在调用g,并添加包含g的局部变量x的堆栈帧(列5)。当 g 返回时,将弹出该帧(第 6 列)。当 f 返回时,将弹出包含与 f 关联的名称的堆栈帧,从而将我们返回到原始堆栈帧(第 7 列)。
请注意,当 f 返回时,即使变量 g 不再存在,该名称曾经绑定到的函数类型的对象仍然存在。这是因为函数是对象,可以像任何其他类型的对象一样返回。因此,z 可以绑定到 f 返回的值,并且函数调用 z () 可用于调用绑定到 f-内名称 g 的函数,即使名称 g 在 f 的上下文之外是未知的。
那么,图 4-5 中的代码打印了什么呢?它打印
对名称的引用出现的顺序并不相关。如果对象绑定到函数体中任何位置的名称(即使它在表达式中出现,然后显示为赋值的左侧),则将其视为该函数的本地对象。
当调用 f 时,它打印 3,但错误消息
在遇到以 g 为单位的打印语句时打印。发生这种情况是因为 print 语句后面的赋值语句导致 x 是 g 的本地值。由于 x 是 g 的本地值,因此在执行 print 语句时,它没有值。
感到困惑了吗?大多数人需要一点时间来了解范围规则。不要让这打扰你。现在,提前充电并开始使用功能。大多数情况下,您只想使用函数本地的变量,而作用域的细微之处将无关紧要。实际上,如果您的程序依赖于一些微妙的范围规则,则可以考虑重写以避免这样做。