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程序设计有所帮助。

(0)

相关推荐

  • 深入理解python中函数传递参数是值传递还是引用传递

    目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是"传对象引用"的方式.实际上,这种方式相当于传值和传引用的一种综合.如果函数收到的是一个可变对象(比如字典 或者列表)的引用,就能修改对象的原始值--相当于通过"传引用"来传递对象.如果函数收到的是一个不可变对象(比如数字.字符或者元组)的引用,就不能 直接修改原始对象--相当于通过"传值"来传递对象. 你可以在很多讨论该问题

  • Python def函数的定义、使用及参数传递实现代码

    Python编程中对于某些需要重复调用的程序,可以使用函数进行定义,基本形式为: def 函数名(参数1, 参数2, --, 参数N): 执行语句函数名为调用的表示名,参数则是传入的参数,可以更具需要定义,也可以没有. # 例1:简单的函数使用 # coding=gb2312 # 定义函数 def hello(): print 'hello python!' # 调用函数 hello() >>> hello python! 函数可以带参数和返回值,参数将按从左到右的匹配,参数可设置默认值

  • Python函数可变参数定义及其参数传递方式实例详解

    本文实例讲述了Python函数可变参数定义及其参数传递方式.分享给大家供大家参考.具体分析如下: python中 函数不定参数的定义形式如下: 1.func(*args) 传入的参数为以元组形式存在args中,如: def func(*args): print args >>> func(1,2,3) (1, 2, 3) >>> func(*[1,2,3]) #这个方式可以直接将一个列表的所有元素当作不定参数 传入(1, 2, 3) 2.func( **kwargs)

  • python进阶教程之函数参数的多种传递方法

    我们已经接触过函数(function)的参数(arguments)传递.当时我们根据位置,传递对应的参数.我们将接触更多的参数传递方式. 回忆一下位置传递: 复制代码 代码如下: def f(a,b,c):     return a+b+c print(f(1,2,3)) 在调用f时,1,2,3根据位置分别传递给了a,b,c. 关键字传递 有些情况下,用位置传递会感觉比较死板.关键字(keyword)传递是根据每个参数的名字传递参数.关键字并不用遵守位置的对应关系.依然沿用上面f的定义,更改调用

  • Python中函数的参数传递与可变长参数介绍

    1.Python中也有像C++一样的默认缺省函数 复制代码 代码如下: def foo(text,num=0):     print text,num foo("asd")  #asd 0 foo("def",100)  #def 100 定义有默认参数的函数时,这些默认值参数 位置必须都在非默认值参数后面. 调用时提供默认值参数值时,使用提供的值,否则使用默认值. 2.Python可以根据参数名传参数 复制代码 代码如下: def foo(ip,port):   

  • Python函数中*args和**kwargs来传递变长参数的用法

    单星号形式(*args)用来传递非命名键可变参数列表.双星号形式(**kwargs)用来传递键值可变参数列表. 下面的例子,传递了一个固定位置参数和两个变长参数. def test_var_args(farg, *args): print "formal arg:", farg for arg in args: print "another arg:", arg test_var_args(1, "two", 3) 结果如下: formal ar

  • 详解Python函数可变参数定义及其参数传递方式

    Python函数可变参数定义及其参数传递方式详解 python中 函数不定参数的定义形式如下 1. func(*args)  传入的参数为以元组形式存在args中,如: def func(*args): print args >>> func(1,2,3) (1, 2, 3) >>> func(*[1,2,3]) #这个方式可以直接将一个列表的所有元素当作不定参数 传入(1, 2, 3) 2.func( **kwargs) 传入的参数为以字典形式存在args中,如: d

  • 浅谈Python中函数的参数传递

    1.普通的参数传递 >>> def add(a,b): return a+b >>> print add(1,2) 3 >>> print add('abc','123') abc123 2.参数个数可选,参数有默认值的传递 >>> def myjoin(string,sep='_'): return sep.join(string) >>> myjoin('Test') 'T_e_s_t' >>>

  • python下函数参数的传递(参数带星号的说明)

    函数参数的使用又有俩个方面值得注意:1.函数参数是如何定义的 2.在调用函数的过程中参数是如何被解析 先看第一个问题,在python中函数参数的定义主要有四种方式: 1.F(arg1,arg2,...) 这 是最常见的定义方式,一个函数可以定义任意个参数,每个参数间用逗号分割,用这种方式定义的函数在调用的的时候也必须在函数名后的小括号里提供个数相等的 值(实际参数),而且顺序必须相同,也就是说在这种调用方式中,形参和实参的个数必须一致,而且必须一一对应,也就是说第一个形参对应这第一个实参.例 如

  • 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,

  • Python中函数参数匹配模型详解

    当我们的函数接收参数为任意个,或者不能确定参数个数时,我们,可以利用 * 来定义任意数目的参数,这个函数调用时,其所有不匹配的位置参数会被赋值为元组,我们可以在函数利用循环或索引进行使用 def f(*args): # 直接打印元组参数 print(args) print('-'*20) # 循环打印元组参数 [print(i) for i in args] ... # 传递一个参数 f(1) print('='*20) # 传递5个参数 f(1, 2, 3, 4, 5) 示例结果: (1,)

  • nodejs中函数的调用实例详解

    一.调用本js文件中的函数 var http = require('http'); http.createServer(function (request,response){ response.writeHead(200, {'Contet-Type':'text/html;charset=utf-8'}); if(request.url!=='/favicon.ico'){ funl(response); response.end(''); } }).listen(8000); consol

  • Python正则表达式中flags参数的实例详解

    目录 flags参数 忽略大小写 多行模式 匹配任何字符 补充:正则表达式中的flags 总结 flags参数 re.I    IGNORECASE    忽略字母大小写 re.L    LOCALE    影响 “w, “W, “b, 和 “B,这取决于当前的本地化设置. re.M    MULTILINE    使用本标志后,‘^’和‘$’匹配行首和行尾时,会增加换行符之前和之后的位置. re.S    DOTALL    使 “.” 特殊字符完全匹配任何字符,包括换行:没有这个标志, “.

  • ES6中Array.copyWithin()函数的用法实例详解

    ES6为Array增加了copyWithin函数,用于操作当前数组自身,用来把某些个位置的元素复制并覆盖到其他位置上去. Array.prototype.copyWithin(target, start = 0, end = this.length) 该函数有三个参数. target:目的起始位置. start:复制源的起始位置,可以省略,可以是负数. end:复制源的结束位置,可以省略,可以是负数,实际结束位置是end-1. 例: 把第3个元素(从0开始)到第5个元素,复制并覆盖到以第1个位置

  • C++中回调函数及函数指针的实例详解

    C++中回调函数及函数指针的实例详解 如何获取到类中函数指针 实现代码: //A类与B类的定义 class A { public: void Test() { cout << "A::Test()" << endl; } }; class B : public A { public: void Test() { cout << "B::Test()" << endl; } }; //定义类的成员函数指针 typedef

  • Kotlin 语言中调用 JavaScript 方法实例详解

    Kotlin 语言中调用 JavaScript 方法实例详解 Kotlin 已被设计为能够与 Java 平台轻松互操作.它将 Java 类视为 Kotlin 类,并且 Java 也将 Kotlin 类视为 Java 类.但是,JavaScript 是一种动态类型语言,这意味着它不会在编译期检查类型.你可以通过动态类型在 Kotlin 中自由地与 JavaScript 交流,但是如果你想要 Kotlin 类型系统的全部威力 ,你可以为 JavaScript 库创建 Kotlin 头文件. 内联 J

  • react中的ajax封装实例详解

    react中的ajax封装实例详解 代码块 **opts: {'可选参数'} **method: 请求方式:GET/POST,默认值:'GET'; **url: 发送请求的地址, 默认值: 当前页地址; **data: string,json; **async: 是否异步:true/false,默认值:true; **cache: 是否缓存:true/false,默认值:true; **contentType: HTTP头信息,默认值:'application/x-www-form-urlenc

  • Linux中的内核链表实例详解

    Linux中的内核链表实例详解 链表中一般都要进行初始化.插入.删除.显示.释放链表,寻找节点这几个操作,下面我对这几个操作进行简单的介绍,因为我的能力不足,可能有些东西理解的不够深入,造成一定的错误,请各位博友指出. A.Linux内核链表中的几个主要函数(下面是内核中的源码拿出来给大家分析一下) 1)初始化: #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0)

随机推荐