- Python程序设计应用教程
- 夏敏捷 张西广
- 2540字
- 2021-03-25 10:05:17
4.2 函数参数
在学习Python语言函数的时候,遇到的问题主要有形参和实参的区别、参数的传递和改变、变量的作用域,下面逐一进行讲解。
4.2.1 函数形参和实参的区别
形参全称为形式参数,在用def关键字定义函数时函数名后面括号里的变量称作形式参数。实参全称为实际参数,在调用函数时提供的值或者变量称作实际参数。例如:
4.2.2 参数的传递
在大多数高级语言当中,对参数的传递方式的理解一直是难点和重点,因为它理解起来并不是那么直观明了,但是如果不理解在编写程序时又极容易出错。下面探讨一下Python中的函数参数的传递问题。
首先在讨论这个问题之前,需要明确一点,即在Python中一切皆对象,变量中存放的是对象的引用。在Python中,之前经常用到的字符串常量、整型常量都是对象。例如:
程序运行结果:
先解释一下函数id()的作用。id(object)函数是返回对象object的id标识(在内存中的地址),id函数的参数类型是一个对象,因此对于这个语句id(2)没有报错,就可以知道2在这里是一个对象。
从结果可以看出,id(x)、id(y)和id(2)的值是一样的,id(z)和id('hello')的值也是一样的。
在Python中一切皆对象,像2、'hello'这样的值都是对象,只不过2是一个整型对象,而'hello'是一个字符串对象。上面的x=2,在Python中实际的处理过程是这样的:先申请一段内存分配给一个整型对象来存储整型值2,然后让变量x去指向这个对象,实际上就是指向这段内存(这里和C语言中的指针类似)。而id(2)和id(x)的结果一样,说明id()函数在作用于变量时,其返回的是变量指向的对象的地址。在这里可以将x看成是对象2的一个引用。同理y=2,变量y也指向这个整型对象2,如图4-1所示。
图4-1 两个变量引用同一个对象示意图
下面讨论一下函数的参数传递问题。
在Python中参数传递采用的是值传递,这和C语言有点类似。对于绝大多数情况下,在函数内部直接修改形参的值不会影响实参。例如:
在有些情况下,可以通过特殊的方式在函数内部修改实参的值。例如:
程序运行结果:
从结果可以看出,执行modify1()之后,n和L都没有发生任何改变;执行modify2()后,n还是没有改变,L发生了改变。因为在Python中参数传递采用的是值传递方式,在执行函数modify1()时,先获取n和L对象的id()值,然后为形参m和K分配空间,让m和K分别指向对象100和对象[1,2,3]。m=2让m重新指向对象2,而K=[4,5,6]让K重新指向对象[4,5,6]。这种改变并不会影响到实参n和L,所以在执行modify1()之后,n和L没有发生任何改变。
在执行函数modify2()时,同理,让m和K分别指向对象2和对象[1,2,3],然而K[0]=0让K[0]重新指向了对象0(注意这里K和L指向的是同一段内存),所以对K指向的内存数据进行的任何改变也会影响到L,因此在执行modify2()后,L发生了改变,如图4-2所示。
图4-2 执行modify2()前后示意图
下面两个例子也是函数内部修改实参的值。
程序运行结果:
再如,修改字典元素值:
程序运行结果:
4.2.3 函数参数的类型
在C语言中,调用函数时必须依照函数定义时的参数个数以及类型来传递参数,否则将会发生错误,这是严格进行规定的。然而,在Python中函数参数定义和传递的方式相比而言则比较灵活。
1.默认值参数
默认值参数能够给函数参数提供默认值。例如:
程序运行结果:
在上面的代码中,分别给a和b指定了默认参数,即如果不给a或者b传递参数时,它们就分别采用默认值。在给参数指定了默认值后,如果传递参数时不指定参数名,则会从左到右依次进行传递。例如,display('world')没有指定'world'是传递给a还是b,则默认从左向右匹配,即传递给a。
默认值参数如果使用不当,会导致很难发现的逻辑错误。
2.关键字参数
前面接触到的函数参数定义和传递方式叫作位置参数,即参数是通过位置进行匹配的,从左到右,依次进行匹配,这个对参数的位置和个数都有严格的要求。而在Python中还有一种是通过参数名字来匹配的,不需要严格按照参数定义时的位置来传递参数,这种参数叫作关键字参数,避免了用户需要牢记位置参数顺序的麻烦。例如:
这段程序是想输出'hello world',可以正常运行。如果按下面这样写,结果可能就不是预期的样子:
可以看出,在Python中默认采用位置参数来传递参数,因此调用函数时必须严格按照函数定义时的参数个数和位置来传递参数,否则将会出现预想不到的结果。下面这段代码采用的就是关键字参数:
下面两句达到的效果是相同的:
可以看到,通过指定参数名字传递参数时,参数位置对结果是没有影响的。
3.任意个数参数
一般情况下在定义函数时,函数参数的个数是确定的,然而某些情况下是不能确定参数的个数的,例如,要存储某个人的名字和它的小名,某些人小名可能有2个或者更多个,此时无法确定参数的个数,只需在参数前面加上'*'或者'**'。例如:
程序运行结果:
'*'和'**'表示能够接受0到任意多个参数,'*'表示将没有匹配的值都放在同一个元组中,'**'表示将没有匹配的值都放在一个字典中。
假如使用'**':
程序运行结果:
假如使用'*':
程序运行结果:
4.2.4 变量的作用域
闭包和函数递归参数
当引入函数的概念之后,就出现了变量作用域的问题。变量起作用的范围称为变量的作用域。一个变量在函数外部定义和在函数内部定义,其作用域是不同的。如果用特殊的关键字定义变量,也会改变其作用域。本节讨论变量的作用域规则。
1.局部变量
在函数内定义的变量只在该函数内起作用,称为局部变量。它们与函数外具有相同名的其他变量没有任何关系,即变量名称对于函数来说是局部的。所有局部变量的作用域是它们被定义的块,从它们的名称被定义处开始。函数结束时,其局部变量被自动删除。下面通过一个例子说明局部变量的使用方法。
在函数fun()中,定义变量x,在函数内部定义的变量作用域都仅限于函数内部,在函数外部是不能够调用的,一般称这种变量为局部变量。所以,在函数外print(x)出现错误提示。
2.全局变量
还有一种变量叫作全局变量,它是在函数外部定义的,作用域是整个程序。全局变量可以直接在函数中使用,但是如果要在函数内部改变全局变量值,必须使用global关键字进行声明。
程序运行结果:
fun2()函数中如果没有global x声明,则编译器认为x是局部变量,而局部变量x又没有创建,从而出错。
注意:
(1)在函数内部直接将一个变量声明为全局变量,而在函数外没有定义,在调用这个函数之后,将变量增加为新的全局变量。
(2)如果一个局部变量和一个全局变量重名,则局部变量会“屏蔽”全局变量,也就是局部变量起作用。