Python3中函数参数传递方式实例详解


Posted in Python onMay 05, 2019

本文实例讲述了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的”

接下来是分析下函数的执行过程,过程如下图

Python3中函数参数传递方式实例详解

分析原话如下

“接下来分析下整个执行过程,查看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的值

Python3中函数参数传递方式实例详解

**这也是因为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程序设计有所帮助。

Python 相关文章推荐
PHP webshell检查工具 python实现代码
Sep 15 Python
python分析apache访问日志脚本分享
Feb 26 Python
python使用正则表达式分析网页中的图片并进行替换的方法
Mar 26 Python
Python中的字符串类型基本知识学习教程
Feb 04 Python
python实现隐马尔科夫模型HMM
Mar 25 Python
django中模板的html自动转意方法
May 27 Python
python通过微信发送邮件实现电脑关机
Jun 20 Python
解决安装python库时windows error5 报错的问题
Oct 21 Python
Python如何读取文件中图片格式
Jan 13 Python
Python ellipsis 的用法详解
Nov 20 Python
详解用selenium来下载小姐姐图片并保存
Jan 26 Python
Python开发之QT解决无边框界面拖动卡屏问题(附带源码)
May 27 Python
python制作填词游戏步骤详解
May 05 #Python
python开发游戏的前期准备
May 05 #Python
Python实现多态、协议和鸭子类型的代码详解
May 05 #Python
用uWSGI和Nginx部署Flask项目的方法示例
May 05 #Python
基于python实现高速视频传输程序
May 05 #Python
Python远程视频监控程序的实例代码
May 05 #Python
Python统计一个字符串中每个字符出现了多少次的方法【字符串转换为列表再统计】
May 05 #Python
You might like
追求程序速度,而不是编程的速度
2008/04/23 PHP
PHP中将ip地址转成十进制数的两种实用方法
2013/08/15 PHP
PHP轻量级数据库操作类Medoo增加、删除、修改、查询例子
2014/07/04 PHP
php通过smtp邮件验证登陆的方法
2016/05/11 PHP
PHP中“=>
2019/03/01 PHP
jQuery Div中加载其他页面的实现代码
2009/02/27 Javascript
使用自定义setTimeout和setInterval使之可以传递参数和对象参数
2009/04/24 Javascript
锋利的jQuery 要点归纳(二) jQuery中的DOM操作(下)
2010/03/23 Javascript
Jquery+ajax请求data显示在GridView上(asp.net)
2010/08/27 Javascript
Knockout text绑定DOM的使用方法
2013/11/15 Javascript
jquery 实现两Select 标签项互调示例代码
2014/09/25 Javascript
JavaScript实现将UPC转换成ISBN的方法
2015/05/26 Javascript
JavaScript中的对象与JSON
2015/07/03 Javascript
谈谈JavaScript的New关键字
2016/08/26 Javascript
AngularJS控制器controller给模型数据赋初始值的方法
2017/01/04 Javascript
node.js基础知识小结
2018/02/26 Javascript
微信小程序中时间戳和日期的相互转换问题
2018/07/09 Javascript
通过封装scroll.js 获取滚动条的值
2018/07/13 Javascript
vue-music 使用better-scroll遇到轮播图不能自动轮播问题
2018/12/03 Javascript
Vue强制组件重新渲染的方法讨论
2020/02/03 Javascript
[02:08:58]2014 DOTA2国际邀请赛中国区预选赛 Ne VS CIS
2014/05/22 DOTA
[01:46]TI4西雅图DOTA2前线报道 中国选手抱团调时差
2014/07/08 DOTA
[37:02]OG vs INfamous 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
可用于监控 mysql Master Slave 状态的python代码
2013/02/10 Python
python绘制条形图方法代码详解
2017/12/19 Python
使用Python实现从各个子文件夹中复制指定文件的方法
2018/10/25 Python
python 字符串追加实例
2019/07/20 Python
Python使用itchat模块实现群聊转发,自动回复功能示例
2019/08/26 Python
python列表推导式入门学习解析
2019/12/02 Python
python2.7使用scapy发送syn实例
2020/05/05 Python
利用css3制作3D样式按钮实现代码
2013/03/18 HTML / CSS
纽约手袋品牌:KARA
2018/03/18 全球购物
给水工程专业毕业生自荐信
2014/01/28 职场文书
餐饮投资计划书
2014/04/25 职场文书
销售顾问工作计划书
2014/09/15 职场文书
避坑之 JavaScript 中的toFixed()和正则表达式
2022/04/19 Javascript