版权申明:原创文章,未经博主同意,不得转载!
在进入正题之前,我们先来了解以下Python中可更改(mutable)与不可更改(immutable)对象以及值传递和引用传递的含义。
可更改对象与不可更改对象
在 python 中一切皆为对象,string, tuple, 和 number 是不可更改对象,而 list,dict 等则是可更改对象。
- 不可更改对象
变量赋值 a=‘Python’ 后再赋值 a=‘学习’,这里实际是新生成一个 string对象 ‘学习’ ,再让 a 指向它,而 ‘Python’ 被丢弃。这里并不是改变a的值,而是相当于新生成了a。 - 可更改对象
变量赋值 list=[1,2,3,4] 后再赋值 list[2]=5 则是将 list 的第三个元素值更改,本身list仍然存在,只是其内部的一部分值被修改了。
什么是值传递和引用传递
值传递与引用传递其实是C系列函数中的说法。那么在C中他们具体含义是什么呢?
-
值传递(Pass-By-Value)
被调函数的形参作为被调函数的局部变量来处理,简单来说就是在栈中新开开辟了内存空间来存放主调函数传过来的实参的值。此时函数中的形参实际为主调函数实参的副本。因此,被调函数中对形参的操作并不会影响外部实参的值。 -
引用传递(Pass-By-Reference)
被调函数的形参同样作为被调函数的局部变量来处理,但此时在栈中开辟的内存空间存放的是主调函数传过来的实参的地址。此时,被调函数对形参的操作实际为间接寻址,即通过堆栈中存放的地址来访问主调函数中的实参变量。因此,被调函数中对形参的操作影响外部实参的值。
Python函数参数传递
前面说了这么多,那么Python中的函数参数传递:到底可以看作是值传递还是引用传递?
答案是:既不是值传递也不是引用传递,具体情况需要分传递的参数是可更改对象还是不可更改对象。
- 传不可变对象实例
def function(a):
a = 10
b = 2
function(b)
print(b) # 结果是 2
在上面代码中,进入函数体后,a的初始值为2,a 和 b 都指向了同一个 Int 对象。
在 a=10 时,则新生成一个 int 值对象 10,并让 a 重新指向了它,b的值并不受影响,如下图。此时的效果类似于值传递,注意只是类似!
- 传可变对象实例
def function( ls ):
"修改传入的列表"
ls.append([1,2,3,4])
print ("函数内取值: ", ls)
return
mylist = [10,20,30]
function( mylist )
print ("函数外取值: ", mylist)
#传入函数的和在末尾添加新内容的对象用的是同一个引用。故输出结果如下:
#函数内取值: [10, 20, 30, [1, 2, 3, 4]]
#函数外取值: [10, 20, 30, [1, 2, 3, 4]]
刚进入function函数的时候,ls与mylist同时指向列表[10,20,30]。如下图,
由于列表是可变对象,在执行ls.append操作的时候,并不需要产生新的对象,因此append直接作用在原始对象[10,20,30]上,所以函数内外的结果是[10, 20, 30, [1, 2, 3, 4]]。如下图,
实战分析
def function(ls):
ls.append(10)
ls = ['a', 'b', 'c']
mylist = [10,20,30]
function(mylist )
print(mylist )
#输出结果为
#[0, 1, 10]
上面结果为什么是[0, 1, 10],为什么不是 [‘a’, ‘b’, ‘c’]? 下面来好好分析分析!
刚进入函数时,ls与mylist同时指向列表[10,20,30]。如下图,
由于列表是可变对象,在执行ls.append操作的时候,并不需要产生新的对象,因此append直接作用在原始对象[10,20,30]上,如下图,
随后,ls = [‘a’, ‘b’, ‘c’],由于此时生成了新的列表对象 [‘a’, ‘b’, ‘c’],ls转而指向该对象。而mylist仍指向原始对象[10,20,30,10]。所以最后输出,mylist并不是[‘a’, ‘b’, ‘c’]。如下图,
总结
在python中函数参数传递,既不是值传递也不是引用传递,具体情况需要分传递的参数是可更改对象还是不可更改对象。关键在于理解在”Python中一切为对象“这句话!
- 如果有什么问题,可以加本人公众号,一同学习进步!