def inc(n):
print(id (n))
n=n+1
print(id(n))
n=3
id(n)
94009708763784
inc(n)
print(n)
94009708763784 94009708763816 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']
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))
传入参数 org_list
为列表,如果为可变对象,按照可变对象传引用的理解。
new_list
和 org_list
指向同一块内存,因此两者的id值输出一致,
任何对 new_list
所执行的内容的操作会直接反应到 org_list
,
也就是说修改 new_list
会导致 org_list
的直接修改,对吧?来看测试例子。
test1=[1,['a',1,3],[2,1],6]
change一me(test1)
140031668769408 140031668769408 [1, '* **', '* **', 6] 140031668769408
print(test1)
[1, '* **', '* **', 6]
test2=[1,2,3,4,5,6,5,[1]]
change一me(test2)
140031668832000 140031668832000 ['a', 'b', 'c'] 140031668809664
print(test2)
[1, 2, 3, 4, 5, 6, 5, [1]]
对于 testl
、 newlist
和 orglist
的表现和理解的传引用确实一致,
最后test1被修改为 [1, '***', '***',6]
,
但对于输入 test2
、 new_list
和 orglist
的id输出在进行列表相关的操作 前是一致的,
但操作之后 newlist
的id值却变为35250664,整个 test2
在调用函数 change_me
后却没有发生任何改变,可是按照传引用的理解期望输出应该是 ['a', 'b', 'c']
,
似乎可变对象传引用这个说法也不恰当,那么Python函数中参数传递的机制到底是怎么样的?
要明白这个槪念,首先要理解: Python中的赋值与理解的C/C++等语言中的賦值的意思并不一样。
如果有如下语句:
a = 5
, b = a
, b = 7
;
分别来看一下在C/C++以及Python中是如何赋值的。
如图3-6所示,C/C++中当执行 b=a
的时候,在内存中申请一块内存并将a的值复制到该内存中;
当执行b=7之后是将b对应的值从5修改为7。
b=7导致b的值从5修改到7
但在Python中赋值并不是复制, b=a
操作使得b与a引用同一个对象。
而 b=7
则是将b 指向对象71如图3-7所示。
通过以下示例来验证上面所述的过程:
a=5
id(a)
94009708763848
b=a
id(b)
94009708763848
b=7
id(b)
94009708763912
id(a)
94009708763848
从输出可以看出, 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)。 函数参数在传递的过程中将整个对象传入,对可变对象的修改在函数外部以及内部都可见, 调用者和被调用者之间共享这个对象,而对于不可变对象,由于并不能真正被修改, 因此,修改往往是通过生成一个新对象然后赋值来实现的。