函数定义和使用的一些情况
(参考教程4.7节,语言手册8.6节)

Python的函数定义和函数调用还有一些情况。

函数的带星号形参和带双星号形参

如果在函数定义的参数表里的一个形参名的前面加一个星号,在这个函数调用时,该形参将约束到所有未匹配的普通实参形成的元组,默认情况是约束到一个空元组。 利用这种形参形式可以定义允许任意个实参的函数。例如,下面定义的是一个可以对任意多个数值求和的函数:
	>>> def mysum (*args) :
		    s = 0
		    for x in args :
			    s += x
		    return s
	>>> f1(1,22,3,4,5)
	35
如果在函数定义的参数表的某个形参名前面加两个星号,在这个函数调用时,该形参将于约束到所有未匹配的关键字实参形成的字典,默认情况下该参数约束到一个空字典。

在一个函数定义的参数表里,可以有任意多个几个普通形参,至多一个带星号的形参,至多一个带双星号的形参。

带星号形参一般都是作为函数定义的参数表里的最后一个形参(因为它要约束到调用式中从这个位置起剩下的所有实参)。如果在函数定义的参数表里,带星号形参之后又出现其他形参,在函数调用时这些形参就只能通过关键字参数的形式取得约束。

在函数调用时,调用中的关键字实参按关键字与同名形参匹配,普通实参按位置与普通形参一一匹配,剩下的普通实参做成一个元组约束到带星号形参,剩下的关键字实参做成一个字典约束到双信号形参。如果没剩下相应种类的实参,带星号实参约束到空元组,带双星号形参约束到空字典。

函数调用的分拆实参(unpacking argument)

在调用函数时可以将元组或表分拆(unpacking)打开,用其元素为函数提供一系列实际参数。分拆参数是一种特殊的实参形式,用在实参表达式之前加一个星号的方式表示。例如:
	>>> a = 1,2,3,4,5
	>>> mysum(*a)
	15
调用时直接写 mysum(a) 将出错。再如
	>>> b = [0, 20, 3]
	>>> range(*b)
	range(0, 20, 3)
允许在一些普通实参之后写分拆实参,用分拆实参为其余形参提供约束值。在分拆实参之后不允许再出现普通实参,但允许出现关键码实参。

还可以用一个适当的字典作为有多个参数的函数的实际参数,这时字典的关键码将被看作函数调用的关键码实参的关键码,与字典关键码对应的值看作实际参数值。描述的形式是把字典写在函数调用的实参表里,前面加两个星号。这种参数可以和函数的其他实参形式混合使用。

最一般情况是:对一个函数调用,首先打开其所有分拆实参,得到一些普通实参和一些关键码实参,按分拆实参在函数调用中出现的位置把这些实参顺序排列在实参列表里。然后先按名字为关键码实参确定与形参的匹配,再按顺序为普通实参确定形参匹配。做完这些之后,剩下的普通实参按顺序做成一个元组,约束到函数的带星号形参,剩下的关键码实参做成一个字典,约束到函数的双星号形参。如果有形参无法得到约束值(实参太少),或者出现多余的实参(只有在函数定义没包含带星号和/或双星号形参时才可能出现这种情况),都是函数调用的实参错误。由于有分拆实参,所以调用的实参错误通常只能在程序运行中确定(不可能在运行前通过静态检查确定)。

注意:虽然Python提供了非常丰富的形参和实参形式,但并不意味着就应该以任意混合的形式使用它们。太复杂的形参和实参形式会使程序中函数调用的意义变得很不清晰,难以理解。应该根据需要合理地使用这些形式。

嵌套的函数定义

允许在一个函数里定义函数,这样定义的函数只在其外围函数的内部有效,只能在该函数的内部使用。在定义所在的函数之外无效。

在一个函数的内部定义函数有时很有意义,主要是可以把只供局部需要的函数定义局部化。例如,我们可能需要定义一个或几个局部函数,它们完成一个函数里一些重要的计算,又能使函数的定义更加结构化,易于理解和维护。课堂上会看到这方面的例子。

函数里的变量

函数通常是定义在全局的环境里,但也可能是定义在另一个函数的内部(见上,函数的嵌套定义)。在一个函数的定义里看到一个变量名,总需要确定它表示的究竟是哪个变量。前面的简单说明更多地借助于直观,下面严格说明这方面的情况。

首先,函数有形参,这些形参的名字当然的局部的。在函数里用到它自己的形参的名字,总表示它的相应形参。我们可以认为,写在参数表里的形参名是一种说明,为这个函数明确定义了一组局部变量,而且这组变量将在函数调用时从实参(表达式)取得初始值。下面的讨论只涉及函数里的非形参的那些变量。

Python的一个基本原则是:赋值即是局部。其意思是说:

  • 如果在一个函数里用到了一个变量,但是在这个函数里没有对这个变量的赋值,那就认为这个变量可能来自这个函数的外层:可以是来自该函数的外围函数(如果存在),也可能是来自全局。如果没有这样的变量,就认为是一个变量无定义错误。也就是说,在一个函数里使用了一个未赋值的变量,系统将向外一层一层层去查找变量的定义。找到第一个就作为它的定义,如果最终也没找到就是错误。

  • 如果在函数里对一个变量赋了值,这个变量就是该函数的局部变量。如果前面没有用到过,就把它看着是新定义的变量。 这种情况有时会造成一个特殊问题,例如:
    >>> def g6 () :
    	y = xx
    	xx = 3
    	return y + xx
    
    >>> g6()
    Traceback (most recent call last):
      File "", line 1, in 
        g6()
      File "", line 2, in g6
        y = xx
    UnboundLocalError: local variable 'xx' referenced before assignment
    
    这个例子说明,如果在一个函数里某个变量被赋值(即使出现在后面),函数里该名字的所有出现都会被当作是这个变量的出现(因此这里认为变量在没赋值之前使用了)。即使函数之外存在具有这个名字的变量(例如全局有变量xx),也不会使用它。

    注意,这种检查是根据程序的正文,而不是根据相应赋值是否执行。例如下面函数定义同样出错:

    def test_nest () :
        y = xx
        if False :
            xx = 1
        return y
    
    虽然其中的 xx = 1 绝不会执行。
如果想在一个函数里修改在该函数的外围有定义的变量,就必须在函数体里说明。这种说明有两种:
  • 用 global 语句(在 global 之后给出变量名)说明有关变量是全局变量;

  • 用 nonlocal 语句(在 nonlocal 之后给出变量名)说明有关变量是在这个函数的外围有定义的变量,按最近定义的原则,先到该函数的直接外围函数中找,如果没找到再到它的直接外围函数里找,这样下去,第一个找到的就是这个变量的定义。如果查找到达全局层还是没有找到该变量,就是变量无定义错误。
本页及相关页面(除另声明者外)由裘宗燕创建维护,可自由用于各种学习活动。其他使用需得到作者许可。