>>> from env_helper import info; info()
页面更新时间: 2023-12-27 09:27:04
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

3.13. 记住函数传参既不是传值也不是传引用

Python中的函数参数到底是传值还是传引用呢?这是许多人在学习过程中会纠结的一个 问题,很多论坛也有这样的讨论。总结来说基本有3个观点:传引用;传值;可变对象传引 用,不可变对象传值。这3个观点到底哪个正确呢?我们逐一讨论。 1)传引用。先来看一个非常简单的例子(请不要因为例子太简单而不以为然,小故事往 往蕴含大道理,它照样能说明问题)。

例一:

>>> def inc(n):
>>>     print(id (n))
>>>     n=n+1
>>>     print(id(n))
>>> n=3
>>> id(n)
9079072
>>> inc(n)
>>> print(n)
9079072
9079104
3

按照传引用的概念,上面的例子期望的输出应该是4,并且inc〇函数里面执行操作 n=n+]的前后n的id值应该是不变的。可是事实是不是这样的呢?非也,从输出结果来看n 的值还是不变,但id(n)的值在函数体前后却不一致。显然,传引用这个说法是不恰当的。

2)传值3来看一个示例 示例二:

>>> def change一list (orginator_list):
>>>     print( "orginator list is:",orginator_list)
>>>     new_list = orginator_list
>>>     new_list.append ("I am new")
>>>     print(" new list is:",new_list)
>>>     return new_list
>>> orginator_list=['a','b','c']
>>> new_list=change一list(orginator_list)
orginator list is: ['a', 'b', 'c']
 new list is: ['a', 'b', 'c', 'I am new']
>>> print(new_list)
['a', 'b', 'c', 'I am new']
>>> print(orginator_list)
['a', 'b', 'c', 'I am new']

传值通俗来讲就是这个意思:你在内存中有一个位置,我也有一个位置,我把我的值 复制给你,以后你做什么就跟我没关系了,我是我,你是你,咱俩井水不犯河水3可是上面 的程序输出根本就不是这么一回事,显然changc_USt〇函数没有遵守约定,调用该函数之后 org丨natorjist也发生了改变,这明显侵犯了 orginatorjist的权利。这么看来传值这个说法也不合适。 3)可变对象传引用,不可变对象传值。这个说法最靠谱,很多人也是这么理解的,但 这个说法到底是否准确呢?再来看一个示例。

示例三:

>>> def change一me (org一list):
>>>     print (id(org一list))
>>>     new_list = org一list
>>>     print(id(new_list))
>>>     if len (new_list) >5:
>>>         new_list = [ 'a','b','c']
>>>     for i,e in enumerate(new_list):
>>>         if isinstance(e,list):
>>>                new_list [ i ] = "* **"# #挤元素为list类搜的替换为***
>>>     print(new_list)
>>>     print(id(new_list))

传人参数〇rg_list为列表.M于可变对象.按照可变对象传引用的理解• new_list和org_ list指向同一块内存,因此两者的id值输出一致,任何对newJist所执行的内容的操作会直 接反应到orgjist,也就是说修改newjist会导致org」ist的直接修改,对吧?来看测试例子。

>>> test1=[1,['a',1,3],[2,1],6]
>>> change一me(test1)
139809374077576
139809374077576
[1, '* **', '* **', 6]
139809374077576
>>> print(test1)
[1, '* **', '* **', 6]
>>> test2=[1,2,3,4,5,6,5,[1]]
>>> change一me(test2)
139809374089800
139809374089800
['a', 'b', 'c']
139809373368264
>>> print(test2)
[1, 2, 3, 4, 5, 6, 5, [1]]

对于testl、ncwjist和orgjist的表现和我们理解的传引用确实一致,最后test]被修改 为[1, ‘V’,6],但对于输人test2、new_list和orgjis丨的id输出在进行列表相关的操作 前是一致的,但操作之后newjist的id值却变为35250664,整个test2在调用函数change_ me〇后却没有发生任何改变,可是按照传引用的理解期望输出应该是[faVbVc1,似乎可变对 象传引用这个说法也不恰当 那么Python函数中参数传递的机制到底是怎么样的?要明白这个槪念,首先要理解: Python中的陚值与我们所理解的C/C++等语言中的賦值的意思并不一样。如果有如下语句: a b * 3&通7 ; 我们分别来看一下在C/C++以及Python中是如何賦值的。 如阁3-6所示,C/C++中当执行b=a的时候,在内存中申请一块内存并将a的值复制到 该内存中;当执行b=7之后是将b对应的值从5修改为7。 b=7导致b的值从5修改到7 image1 但在Python中赋值并不是复制,b=a操作使得b与a引用同一个对象。而b=7则是将b 指向对象71如图3-7所示。 image2 我们通过以下示例来验证上面所述的过程:

>>> a=5
>>> id(a)
9079136
>>> b=a
>>> id(b)
9079136
>>> b=7
>>> id(b)
9079200
>>> id(a)
9079136

从输出可以看出,b=a赋值后b的id()输出和a—样,但b=7操作后b指向另外一块空 间。可以简单理解为,b=a传递的是对象的引用,其过程类似于贴”标签”,5和7是实实在 在的内存空间,执行a=5相当于申请一块内存空间代表对象5并在上面贴上标签a,这样a 和5便綁定在一起了。而b=a相当于对标签a创建了一个别名,因此它们实际都指向5。但 b=7操作之后标签b重新贴到7所代表的对象上去了,而此时5仅有标签a

理解了上述背景,再回头来看看前面的例子就很好理解了。对于示例一,n=n+1,由于 n为数字,是不可变对象,n+1会重新申请一块内存,其值为n+1,并在函数体中创建局部 变量n指向它。当调用完函数inc(n)之后,函数体中的局部变量在函数外不并不可见,此时的n代表函数体外的命名空间所对应的n,值还是3。而在示例三中,当org_list的长度大于 5的时候,new_list=['a','b','c']操作重新创建了一块内存并将new_list指向它。当传人参数为 test2=[1,2,3,4,5,6,[1]]的时候,函数的执行并没有改变该列表的值。

因此,对于Python函数参数是传值还是传引用这个问题的答案是;都不是。正确的叫法 应该是传对象(call by object)或者说传对象的引用(call-by-object-reference)。函数参数在传 递的过程中将整个对象传人,对可变对象的修改在函数外部以及内部都可见.调用者和被调 用者之间共享这个对象.而对丁不可变对象.由于并不能真正被修改,因此,修改往往是通 过生成一个新对象然后賦值来实现的。