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 相关文章推荐
Python中使用Boolean操作符做真值测试实例
Jan 30 Python
python 出现SyntaxError: non-keyword arg after keyword arg错误解决办法
Feb 14 Python
Python读写及备份oracle数据库操作示例
May 17 Python
解决python os.mkdir创建目录失败的问题
Oct 16 Python
flask-restful使用总结
Dec 04 Python
python实现美团订单推送到测试环境,提供便利操作示例
Aug 09 Python
python如何保证输入键入数字的方法
Aug 23 Python
解决python对齐错误的方法
Jul 16 Python
Django3中的自定义用户模型实例详解
Aug 23 Python
Pycharm 如何一键加引号的方法步骤
Feb 05 Python
Pytorch 如何实现LSTM时间序列预测
May 17 Python
PyTorch dropout设置训练和测试模式的实现
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
PHP5.2下chunk_split()函数整数溢出漏洞 分析
2007/06/06 PHP
php读取csv实现csv文件下载功能
2013/12/18 PHP
PHP邮件群发机实现代码
2016/02/16 PHP
PHP中Notice错误常见解决方法
2017/04/28 PHP
如何直接访问php实例对象中的private属性详解
2017/10/12 PHP
{}与function(){}选用空对象{}来存放keyValue
2012/05/23 Javascript
js使下拉列表框可编辑不止是选择
2013/12/12 Javascript
关于JavaScript中name的意义冲突示例介绍
2014/05/29 Javascript
浅谈JavaScript中的Math.atan()方法的使用
2015/06/14 Javascript
JavaScript实现in-place思想的快速排序方法
2016/08/07 Javascript
浅谈EasyUI常用控件的禁用方法
2016/11/09 Javascript
详解node HTTP请求客户端 - Request
2017/05/05 Javascript
yarn的使用与升级Node.js的方法详解
2017/06/04 Javascript
实现两个文本框同时输入的实例
2017/09/25 Javascript
vue.js select下拉框绑定和取值方法
2018/03/03 Javascript
vue项目中使用Hbuilder打包app 设置沉浸式状态栏的方法
2018/10/22 Javascript
vue使用微信JS-SDK实现分享功能
2019/08/23 Javascript
微信小程序实现图片翻转效果的实例代码
2019/09/20 Javascript
关于vue2强制刷新,解决页面不会重新渲染的问题
2019/10/29 Javascript
[01:45]2014DOTA2 TI预选赛预选赛 战前探营!
2014/05/21 DOTA
Python中使用PIPE操作Linux管道
2015/02/04 Python
Python实现Linux中的du命令
2017/06/12 Python
Python实现基于POS算法的区块链
2018/08/07 Python
python环境路径配置以及命令行运行脚本
2019/04/02 Python
基于Keras的格式化输出Loss实现方式
2020/06/17 Python
推荐技术人员一款Python开源库(造数据神器)
2020/07/08 Python
利用CSS3实现文本框的清除按钮相关的一些效果
2015/06/23 HTML / CSS
你在项目中用到了xml技术的哪些方面?如何实现的?
2014/01/26 面试题
新领导上任欢迎词
2014/01/13 职场文书
《两个铁球同时着地》教学反思
2014/02/13 职场文书
美容院店长岗位职责
2014/04/08 职场文书
三方合作协议书范本
2014/04/18 职场文书
2015年乡镇工会工作总结
2015/05/19 职场文书
债务纠纷起诉书
2015/05/20 职场文书
资产移交协议书
2016/03/24 职场文书
如何利用Python实现一个论文降重工具
2021/07/09 Python