Python函数参数传递机制:传值与传引用的本质解析
本文深入解析Python函数参数传递机制,澄清传值与传引用的概念。通过代码示例说明Python采用“传对象引用”模式:对不可变对象(如整数)的操作类似传值,不影响原变量;对可变对象(如列表、字典)的操作类似传引用,可修改原对象。
参数传递机制概述
首先,我们需要了解函数参数传递的基本机制,即什么是传值和传引用。
函数参数传递的本质,是调用函数与被调用函数在交互时进行数据通信的方式。目前主要有两种基本的传递机制:值传递和引用传递。
在值传递(pass-by-value)模式下,被调用函数的形式参数被视为局部变量。系统会在栈内存中开辟新空间,用于存储主调函数传入的实际参数值,这相当于创建了一个实参的副本。因此,被调用函数对形式参数的任何修改仅作用于局部副本,不会改变主调函数中实参的值。
在引用传递(pass-by-reference)模式下,虽然形式参数同样作为局部变量在栈中分配空间,但其中存储的是实参变量的内存地址。被调用函数对形式参数的操作会被转化为间接寻址,即通过栈中存储的地址去访问主调函数中的实参变量。因此,对形式参数的修改会直接反映在主调函数的实参上。
Python中的参数传递行为
那么,在Python语言中,实际采用的是哪种机制呢?
让我们通过一个简单的示例来观察。
from ctypes import *
import os.path
import sys
def test(c):
print "test before "
print id(c)
c+=2
print "test after +"
print id(c)
return c
def printIt(t):
for i in range(len(t)):
print t[i]
if __name__=="__main__":
a=2
print "main before invoke test"
print id(a)
n=test(a)
print "main afterf invoke test"
print a
print id(a)
运行上述代码后,输出结果如下:
>>>
main before invoke test
test before
test after +
main afterf invoke test
39601564
通过id函数可以获取对象的内存地址。从上述示例可以看出,变量a作为参数传递给test函数时,传递的是a的引用(即内存地址)。因此,函数内部变量c的地址与变量a的地址相同。然而,在函数内部对c进行赋值运算(c+=2)时,c指向了新的内存地址(值为4),而原内存地址(值为2)依然存在。由于a仍然指向原来的内存地址,因此打印a时,其值仍为2。
如果上述解释仍难以理解,请参考下面的另一个示例。
>>> a=1
>>> b=1
>>> id(a)
>>> id(b)
>>> a=2
>>> id(a)
变量a和b均为int类型,值均为1,且它们的内存地址相同。这表明在Python中,多个引用可以指向同一个内存对象。当给a重新赋值为2后,再次查看a的内存地址,发现其地址已发生变化。

基于前面的示例,我们可以这样理解:

这是否意味着Python函数传参就是传引用?既然传参后在被调函数内修改不影响主调函数的实参,那又是为何?让我们再看一个例子。
from ctypes import *
import os.path
import sys
def test(list2):
print "test before "
print id(list2)
list2[1]=30
print "test after +"
print id(list2)
return list2
def printIt(t):
for i in range(len(t)):
print t[i]
if __name__=="__main__":
list1=["loleina",25,'female']
print "main before invoke test"
print id(list1)
list3=test(list1)
print "main afterf invoke test"
print list1
print id(list1)
实际运行结果如下:
>>>
main before invoke test
test before
test after +
main afterf invoke test
['loleina', 30, 'female']
我们发现,虽然传递方式看似相同,但第二个变量(列表)的值却发生了变化。这是为什么呢?
原因在于Python中的序列类型:列表是一个可变对象。基于list1=[1,2]执行list1[0]=[0]操作前后,查看list1的内存地址,发现其地址保持不变。
>>> list1=[1,2]
>>> id(list1)
>>> list1[0]=[0]
>>> list1
[[0], 2]
>>> id(list1)
字典同样属于可变对象:
>>> def fun2(num1,l1,d1):
... num1=123
... l1[0]=123
... d1['a']=123
... print("inside:","num1=%f,l1=%s,d1=%s"%(num1,l1,d1))
...
>>> num=111
>>> l=[1,1,1]
>>> d={'a':111,'b':0}
>>> print("before:","num=%f,l=%s,d=%s"%(num,l,d))
before: num=111.000000,l=[1, 1, 1],d={'a': 111, 'b': 0}
>>> fun2(num,l,d)
inside: num1=123.000000,l1=[123, 1, 1],d1={'a': 123, 'b': 0}
>>> print("after:","num=%f,l=%s,d=%s"%(num,l,d))
after: num=111.000000,l=[123, 1, 1],d={'a': 123, 'b': 0}
Python参数传递结论
结论:Python不允许程序员手动选择采用传值还是传引用。Python参数传递采用的是“传对象引用”(pass-by-object-reference)方式。这种方式可以看作是传值和传引用的综合体现。
如果函数接收到的是一个可变对象(如字典或列表)的引用,就可以修改该对象的原始内容——这相当于通过“传引用”的方式传递对象。
如果函数接收到的是一个不可变对象(如整数、字符串或元组)的引用,则无法直接修改原始对象——这相当于通过“传值”的方式传递对象。
以上就是本文的全部内容,希望对大家的学习有所帮助,也欢迎大家继续支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













