Python3中函数参数传递方式实例详解
本文实例讲述了python3中函数参数传递方式。分享给大家供大家参考,具体如下:
之前在看北理工嵩天等老师的python3的课程,在第五周中老师讲到了函数的调用传递。老师讲了这样一个例子
#处理多个银行账户的余额信息 def addinterest(balances, rate): for i in range(len(balances)): balances[i] = balances[i] * (1+rate) def test(): amounts = [1000, 105, 3500, 739] rate = 0.05 addinterest(amounts, rate) print(amounts) test()
在这个例子中可以看到为了处理多个银行账户设置了amounts这个列表,老师的原话是这样的:
“在test函数中一开始设置了amounts为4个值的列表,然后将amounts作为第一个参数传递给函数addinterest,并调用执行该函数,最后打印输出amounts的结果,运行结果如下:
[1050.0,110.25,3675.0,775.95]
”
然后礼欣老师得出结论,以下为原话
“在这个例子中可以看到,amounts变量的值好像是被修改了,但是函数是不能改变变量本身即amounts的”
接下来是分析下函数的执行过程,过程如下图
分析原话如下
“接下来分析下整个执行过程,查看amounts输出结果是如何发生变化的。首先函数test的前两条语句建立了两个变量amounts和rate,然后控制传递给addinterest,这里amounts是一个包含4个整数类型值的列表对象,以实参的形式传递给函数addinterest形参balances,下一步执行函数addinterest,从0到length-1范围执行循环,并更新balances的值。”
重点来了:原话如下
“图中旧值 [1000, 105, 3500, 739]
并没有改变,只是python又创建了一组新值[1050.0,110.25,3675.0,775.95]
,并且使列表对象指向该组新值,而旧值会在python的垃圾数据回收的时候被清除掉③,从图中我们可以清楚的看出,为什么包含列表参数的程序addinterest修改了列表的值?但程序addinterest结束时存储在amounts中的是新balances的值,实际上变量amounts从来没有被改变过。”
“它(amounts,作者注)仍然指向的是调用addinterest函数之前的同一个列表,只是当控制返回到调用函数中时,列表呈现了被修改的状态”②
最后是得出结论,原话如下:
“通过上述过程我们可以了解到:python的参数是通过值来传递的。但是如果变量是可变对象,比如是列表或者是图形对象,返回到调用程序后,该对象会呈现出被修改的状态。”
^_^
注:课程原始视频部分结束。
看了老师的这段讲解之后产生了很多疑问:在前面(①处)讲的amounts是不能被修改的,但是在(②处)又说列表呈现了被修改的状态,这不是自相矛盾吗?在(③)处讲列表创建了新值并且使列表指向了新值,这里不就是说amounts发生了改变吗?怎么能说没变呢?最后结论也是列表呈现出了被修改的状态。这个结论云山雾绕,看得人似懂非懂。
那在python3中参数变量是列表,在调用函数完返回后到底是被修改了还是没被修改呢?
为了弄清这个问题,我做了一个实验,id()可以查看变量在内存中的地址,这个值相当于一个对象的“身份证”。
# 处理多个银行账户的余额信息 def addinterest(balances, rates): print() print("第二处", id(balances)) for i in range(len(balances)): balances[i]= balances[i]*(1+rates) print() print("第三处",id(balances)) def test(): amounts = [1000,105,3500,739] print() print("第一处",id(amounts)) rate = 0.05 addinterest(amounts, rate) print() print(amounts) print() print("第四处",id(amounts)) test()
输出结果:
第一处 41203656
第二处 41203656
第三处 41203656
第三处 41203656
第三处 41203656
第三处 41203656
[1050.0, 110.25, 3675.0, 775.95]
第四处 41203656
在这个实验中可以清楚的看到,amounts这个对象的身份证号码在整个程序运行过程中从未变过,而非视频中老师讲的创建了新的列表对象。所以amounts作为一个列表对象在程序运行过程中是被直接修改了,是的就是直接被修改了,而非指向新balances的值。为什么可以得出这一结论?我们可以看下第一、三处的id,在未进入函数之前id是41203656(第一处),进入函数之后对象id仍然未变,函数运行完返回之后对象id仍然未变!
所以结论应该这样写会比较清楚:
改变参数值值的函数:
实参传给形参时,python的参数是通过值来传递的;
如果变量是可变对象(如列表或者图形对象),该对象会在函数中会被直接修改,返回到调用程序后也是被修改后的状态。
那是不是python3中函数都是像这种传递方式呢?我们对课程视频中的另一个例子做一个简单的修改。
# 计算单个银行账户余额 def addinterest(balance, rate): print("第二处", id(balance)) newbalance = balance * (1 + rate) print() print("第三处", id(balance)) print() print("第四处", id(newbalance)) return newbalance def main(): amount = 1000 print("第一处", id(amount)) print() rate = 0.05 amount = addinterest(amount, rate) print() print("第五处", id(amount)) print() print(amount) print("第六处", id(amount)) main()
运行结果如下:
第一处 33533648
第二处 33533648
第三处 33533648
第四处 33563344
第五处 33563344
1050.0
第六处 33563344
不是说好的直接修改的吗?怎么身份证又变了?其实这里的对象amount是个常数,即为不可变对象,当在函数中要对对象进行处理时,由于对象不可变,只能新建一个新对象,然后return出新的对象了。
这个也就是目前网络上大部分博客的结论:
1、不可变对象作为函数参数,python通过值传递;
2、 可变对象作为函数参数,python通过引用传递。
注:python中,数值类型(int和float)、字符串str、元组tuple都是不可变类型。而列表list、字典dict、集合set是可变类型。
(但是也有博客把这两个结论搞反了)
但是也有博客提出了一个类似这样的例子
def change(val): val = val + [10] nums = [0, 1] change(nums) print(nums)
输出结果为
[0, 1]
其实这里是写的不严谨,不能直接用加号添加列表元素
可以改为这样
def change(val): newval = [10] val= val + newval nums = [0, 1] change(nums) print(nums)
但是输出结果还是
[0, 1]
难道上面的结论不对吗?
其实这里要补充另外一种情况:对于可变对象作为函数参数,且参数不指向其他对象时,相当于引用传递;否则,若参数指向其他对象,则对参数变量的操作并不影响原变量的对象值
函数里的参数变量val指向了与nums不同的内存变量,所以函数里的参数变量val不影响原变量nums的值
**这也是因为python的特性” 变量无类型,对象有类型 “。
变量是对内存空间的引用,当参数变量和原变量指向不同的内存空间时,操作互不影响。**
用下面这个看下
def change(val): newval = [10] print("第二处",id(val)) val = val + newval print("第三处",id(val)) nums = [0, 1] print("第一处",id(nums)) change(nums) print("第四处",id(nums)) print(nums)
运行结果如下:
第一处 39695944
第二处 39695944
第三处 39710024
第四处 39695944
[0, 1]
可以看到第一处的nums和第二处的val的内存地址完全一样,然后执行到第三处时,由于函数内val重新指向了别的内存变量,所以内存地址不同。但是最后结果要输出变量nums,即第一处第二处内存地址的值,所以和第三处的val就没关系了。其实这里的val是没有返回值的。
想要直接在列表中添加元素可以写成这样:
def change(val): val.append(10) nums = [0, 1] change(nums) print(nums)
输出结果是
[0, 1, 10]
关于变量无类型,对象有类型可以这样理解:只有放在内存空间中的对象(也就是数据)才有类型,而变量是没有类型的。
如果还是不明白可以做这样一种比喻:变量就好比钓鱼者,湖水就好像内存,里面有各种各样的鱼,它们就是对象。钓鱼者(变量)的任务就是用某种方式把自己和鱼(对象)通过鱼线连接起来。那么,鱼(对象)是有类型的,有鲢鱼、鲫鱼、带鱼。钓鱼者(变量)没有类型,他钓到不同类型的鱼(对象)。
用钓鱼的比喻解释下上面的例子
def change(val): newval = [10] val= val + newval nums = [0, 1] change(nums) print(nums)
1、钓鱼人已经钓了一桶鱼用nums桶装着,nums桶可以装很多鱼。
2、现在提着这个nums桶继续在湖里钓鱼,这时候nums桶暂时叫做装鱼桶val,突然钓鱼人钓了一条大鱼,发现装鱼桶val装不下,于是钓鱼人又在渔具店买了另一个大的装鱼桶val,把大鱼和之前的鱼一块装了。
钓鱼活动结束。3、最后要看看那个叫nums的桶有哪些鱼,这时候当然只能看之前的情况。
即这个结论:对于可变对象作为函数参数,且参数不指向其他对象时,相当于引用传递;否则,若参数指向其他对象,则对参数变量的操作并不影响原变量的对象值。
同样的针对其他两个结论,也可以用这个比喻解释:
def change( val): newval = val + 10 return newval num = 1 num = change(num) print(num)
1、钓鱼人手上的东西num是个小蚯蚓。
2、钓鱼人拿着num去湖边钓鱼,小蚯蚓被大鱼吃了,钓鱼人钓到了一条大鱼,钓鱼人拿着鱼回家。
钓鱼活动结束。3、问钓鱼人手上现在拿着什么东西num?当然是一条大鱼。
def change(val): val.append(10) nums = [0, 1] change(nums) print(nums)
1、钓鱼人提着一个叫nums的桶,桶里装着2条鱼
2、钓鱼人来到湖边钓鱼,此时桶暂时叫装鱼桶val,钓鱼人钓到了一条鱼放进装鱼桶val。
钓鱼活动结束。3、看看钓鱼人桶里的有几条鱼。
总结来说:
**对于不可变对象作为函数参数,相当于c系语言的值传递;
对于可变对象作为函数参数,且参数不指向其他对象时,相当于c系语言的引用传递。
对于可变对象作为函数参数,参数指向其他对象,对参数变量的操作不影响原变量的值。**
关于python相关内容感兴趣的读者可查看本站专题:《python函数使用技巧总结》、《python面向对象程序设计入门与进阶教程》、《python数据结构与算法教程》、《python字符串操作技巧汇总》、《python编码操作技巧总结》及《python入门与进阶经典教程》
希望本文所述对大家python程序设计有所帮助。