11.6 可变长度的参数

可能会有需要用函数处理可变数量参数的情况。这时可使用可变长度的参数列表。变长的参数在函数声明中不是显式命名的,因为参数的数目在运行时之前是未知的(甚至在运行的期间,每次函数调用的参数的数目也可能是不同的),这和常规参数(位置和默认)明显不同,常规参数都是在函数声明中命名的。由于函数调用提供了关键字以及非关键字两种参数类型,Python用两种方法来支持变长参数。

在11.2.4小节中,我们了解了在函数调用中使用和*符号来指定元组和字典的元素作为非关键字以及关键字参数的方法。在这个部分中,我们将再次使用相同的符号,但是这次在函数的声明中,表示在函数调用时接收这样的参数。这语法允许函数接收在函数声明中定义的形参之外的参数。

11.6.1 非关键字可变长参数(元组)

当函数被调用的时候,所有的形参(必须的和默认的)都将值赋给了在函数声明中相对应的局部变量。剩下的非关键字参数按顺序插入到一个元组中便于访问。可能你对C中的“varargs”(比如、va_list、va_arg和省略号[…])很熟悉。Python提供了与之相等的支持——迭代过所有的元组元素和在C中用va_arg是相同的。对于那些不熟悉C或者“varargs”的人,这仅仅代表了在函数调用时,接受一个不定(非固定)数目的参数。

可变长的参数元组必须在位置和默认参数之后,带元组(或者非关键字可变长参数)的函数普遍的语法如下:

11.6 可变长度的参数 - 图1

星号操作符之后的形参将作为元组传递给函数,元组保存了所有传递给函数的“额外”的参数(匹配了所有位置和具名参数后剩余的)。如果没有给出额外的参数,元组为空。

正如我们先前看见的,只要在函数调用时给出不正确的函数参数数目,就会产生一个TypeError异常。通过末尾增加一个可变的参数列表变量,我们就能处理当超出数目的参数被传入函数的情形,因为所有的额外(非关键字)参数会被添加到变量参数元组(额外的关键字参数需要关键字变量参数[参见下一小节])。正如预料的那样,由于和位置参数必须放在关键字参数之前一样的原因,所有的形式参数必须先于非正式的参数之前出现。

11.6 可变长度的参数 - 图2

11.6 可变长度的参数 - 图3

我们现在调用这个函数来说明可变参数元组是如何工作的。

11.6 可变长度的参数 - 图4

11.6.2 关键字变量参数(字典)

在我们有不定数目的或者额外集合的关键字的情况中,参数被放入一个字典中,字典中键为参数名,值为相应的参数值。为什么一定要是字典呢?因为每个参数——参数的名字和参数值——都是成对给出,用字典来保存这些参数自然就最适合不过了。

这给出使用了变量参数字典来应对额外关键字参数的函数定义的语法:

11.6 可变长度的参数 - 图5

为了区分关键字参数和非关键字非正式参数,使用了双星号()。是被重载了的以便不与幂运算发生混淆。关键字变量参数应该为函数定义的最后一个参数,带**。我们现在展示一个如何使用字典的例子:

11.6 可变长度的参数 - 图6

在解释器中执行这个代码,我们得到以下输出。

11.6 可变长度的参数 - 图7

关键字和非关键字可变长参数都有可能用在同一个函数中,只要关键字字典是最后一个参数并且非关键字元组先于它之前出现,正如在如下例子中的一样:

11.6 可变长度的参数 - 图8

在解释器中调用我们的函数,我们得到如下的输出:

11.6 可变长度的参数 - 图9

11.6.3 调用带有可变长参数对象函数

在上面的11.2.4部分中,我们介绍了在函数调用中使用和*来指定参数集合。接下来带着对函数接受变长参数的些许偏见,我们会向你展示更多那种语法的例子。

我们现在将用在前面部分定义的,我们的老朋友newfoo(),来测试新的调用语法。我们第一个对newfoo()的调用将会使用旧风格的方式来分别列出所有的参数,甚至跟在所有形式参数之后的变长参数:

11.6 可变长度的参数 - 图10

我们现在进行相似的调用;然而,我们将非关键字参数放在元组中将关键字参数放在字典中,而不是逐个列出变量参数:

11.6 可变长度的参数 - 图11

最终,我们将再另外进行一次调用,但是是在函数调用之外来创建我们的元组和字典。

11.6 可变长度的参数 - 图12

11.6 可变长度的参数 - 图13

注意我们的元组和字典参数仅仅是被调函数中最终接收的元组和字典的子集。额外的非关键字值‘3’以及‘χ’和‘y’关键字对也被包含在最终的参数列表中,而它们不是‘’和‘*’的可变参数中的元素。

之前的1.6,过去变长对象只能通过apply()函数传递给被调用函数,现在的调用语法已经可以有效取代apply()的使用。下面演示了如何使用了这些符号来把任意类型任意个数的参数传递给任意函数对象。

函数式编程举例

函数式编程的另外一个有用的应用出现在调试和性能测量方面上。你正在使用需要每晚都被完全测试或通过回归,或需要给对潜在改善进行多次迭代计时的函数来工作。你所要做的就是创建一个设置测试环境的诊断函数,然后对有疑问的地方,调用函数。因为系统应该是灵活的,所以想testee函数作为参数传入。那么这样的函数对,timeit()和testit(),可能会对如今的软件开发者有帮助。

我们现在将展示这样的一个testit()函数的例子的源代码(见例11.5)。我们将留下timeit()函数作为读者的练习(见习题11.12)。

该模块给函数提供了一个执行测试的环境。testit()函数使用了一个函数和一些参数,然后在异常处理的监控下,用给定的参数调用了那个函数。如果函数成功的完成,会返回True和函数的返回值给调用者。任何的失败都会导致False和异常的原因一同被返回。

(Exception是所有运行时刻异常的根类:复习第10章以获得更详细的资料。)

例11.5 测试函数(testit.py)

testit()用其参数地调用了一个给定的函数,成功的话,返回一个和那函数返回值打包的True的返回值,或者False和失败的原因。

11.6 可变长度的参数 - 图14

11.6 可变长度的参数 - 图15

单元测试函数test()在一个为4个数字的输入集合运行了一个数字转换函数的集合。为了确定这样的功能性,在测试中有两个失败的案例。这里是运行脚本的输出:

11.6 可变长度的参数 - 图16