
3.1.2 函数参数
函数参数分为实参和形参,实参是调用函数时的实际参数,形参是定义函数的形式参数,例如在上面例子中定义函数total(n)时的参数n是形参,而调用函数xx=total(x)时的参数x是实参。形参可以理解成定义函数时参数暂时的占位,在调用函数时,把实参的真实值放到形参的位置。在定义函数和调用函数时,需要注意以下几点。
1.不可变数据和可变数据的传递
当实参数据传递给形参数据时,是把数据在内存中的地址传递给形参。当实参是不可变数据时,例如常数、字符串、元组等,在实参数据传递给形参后,如果在函数体内改变了形参数据,Python会在内存中新产生一个数据区用于存储新数据,并把形参指向该地址,而实参仍指向原来的数据,所有形参的数据不会改变实参的数据。而对于可变的数据,如列表、字典等,形参和实参都指向原数据,当改变形参数据时,会改变原数据地址内的数据,从而实参数据也跟着改变了。
下面的代码是改变形参数据的实例,分别给形参传递一个整数、字符串和列表,在函数体内改变形参的值,对比调用函数前后实参值改变情况和形参值及地址的改变情况。

运行上面代码,可以得到如下输出。可以看出当调用double()函数传递一个整数和字符串时,实参在调用函数前和调用函数后值和地址都没有发生变化,而形参在函数体内改变值后,值和地址都发生变化。而传递一个列表时,实参在调用函数前和调用函数后地址没有变化,值发生变化;形参在函数体内改变值后值发生变化,而地址没有发生变化。

解决这个问题的办法是在函数体内新建一个列表,然后把形参的数据用extend()方法移到新列表中,对新列表的数据进行改变。

运行后得到下面的结果,实参值没有发生变化。如果在自定义函数中只是提供数据用于其他运算,不改变形参的值,就无须这么做。

2.关键字参数
定义函数时,每个形参在函数体中的作用是不一样的。在调用函数用实参传递给形参时,实参的个数和位置与形参的个数和位置须一致,否则会出现异常或计算结果不合理的情况。如果在调用函数时,实参的顺序与形参的顺序不一样,就会产生函数体内部计算异常。例如本该传递一个整数的形参,由于实参顺序错误,给这个形参传递了一个字符串,那么本该用整数参与的计算却用字符串参与计算,势必会产生问题。为解决这个问题,在调用函数时使用关键字参数。关键字参数是指在调用函数时,用形参的名字作为关键字确定传递给形参的值,不需要与函数定义时形参的位置和顺序一致,只要把形参名字写正确,这样还提高了程序的可读性。例如某个函数定义时函数名和形参为area(side1,side2,height),在调用函数时,可以用area(height=value1,side1=value2,side3=value2),实参的顺序与定义函数时的形参顺序可以不一致。例如下面计算梯形面积的例子,需要输入上下两个底的长度和梯形的高,函数返回梯形面积。

3.形参的默认值
在定义函数时,可以给形参设置默认值,在调用函数时可以不给形参传递值,而是使用默认值。例如Python的内置函数print()的原型是print(value,...,sep='',end='\n', file=sys.stdout, flush=False),形式参数sep、end、file和flush都是有默认值的,在使用print()函数时,一般不用设置这些参数的值,直接使用默认值。在定义函数时,有默认值的参数需要放到没有默认值的参数的后面。下面的代码是计算函数的值,其中k和c是常量,默认值k=1.0,c=0.0。

4.数量可变的参数
有些时候,调用函数时需要输入的函数参数不确定,由实际情况决定,这在类的函数中经常用到。参数数量可变的函数定义分为两种,一种是在定义函数时用*parameter1来定义可变数量的参数,另一种是用**parameter2定义可变数量的关键字参数。当用*parameter1定义形参时,可以接受任意多个实参,此时形参parameter1是一个元组,实参成为parameter1的元素,用len(parameter1)可以获取传递过来的实参的数量,通过元组的索引形式parameter1[index]在函数体内读取实参传过来的值。当用**parameter2定义可变数量的关键字参数时,形参parameter2是字典,调用函数时实参形式应该为name1= value1,name2=value2,...,nameN=valueN,此时实参值的关键字namei将作为字典parameter2的键,valuei将作为对应的值,在函数体内可以通过字典的方法parameter2. keys()获取字典的键,通过键parameter2[key]获取键对应的值,通过parameter2.items()获取字典键和值。
下面是一个求和函数,用*parameter形式定义形参,调用函数时可以输入任意多个实参。

在类的函数定义中,经常使用**parameter的形式定义输入参数,例如下面的描述人特征的例子。

下面的例子既有*定义的参数,也有**定义的参数。
