Python函数传参本质:从内存地址看值传递与引用传递

叶初
叶初
发布时间:2026-10-13 | 2人浏览过
原创

本文深入解析Python函数参数传递机制,指出简单区分值传递或引用传递并不准确。通过id()查看内存地址,揭示不可变对象共享地址、可变对象创建即分配新地址的特性。文章通过代码实验说明,函数内对可变对象的修改(如pop)与重新赋值(如+)对原对象的影响不同,核心在于Python动态语言特性下的内存分配策略。

目前网络上许多技术博客对此问题的结论往往如下:

Python并未允许程序员在传值与传引用之间进行选择,其参数传递机制实质上采用的是“传对象引用”。然而,这种机制并非单纯的传引用,而是传值与传引用的综合体现。当函数接收的是可变对象(如字典或列表)的引用时,可以直接修改该对象的原始内容,这类似于“传引用”的效果;而当接收的是不可变对象(如整数、字符串或元组)的引用时,则无法直接修改原始对象,这类似于“传值”的效果。

上述观点在大量探讨该话题的博客中广泛流传。

然而,在实际编码操作中,我发现了一个值得注意的现象:

l=[1,2,3]
def a(x):
  x=x+[4]
a(l)
print(l)

执行上述代码后,输出结果为:

[1,2,3]

为何会出现这种情况?既然list是可变对象,按照前述结论,传递方式应为引用传递,我理应在函数内部修改它。难道预期输出不应该是[1, 2, 3, 4]吗?

我认为,之前引用的那些主流博主的结论虽然常见,但极易引起误解,且未触及本质,读起来令人困惑。

经过后续多次实验验证,我得出以下结论:

在Python中,单纯讨论“值传递”还是“引用传递”并无太大意义。要真正解释这些现象,关键在于理解Python在赋值过程中(针对可变与不可变对象)是如何分配内存地址的。

因此,我们将暂时搁置值传递与引用传递的争论。

让我们进行一个简单的小实验,利用id()函数查看变量在内存中的地址:

l1=[1,2,3]
l2=[1,2,3]
a=1
b=1
print(id(l1))
print(id(l2))
print(id(a))
print(id(b))

在我的电脑环境中,运行结果如下:

12856594504
12856915080
1643643344
1643643344

由此可见,对于可变对象list而言,即使两个列表的内容完全相同,Python也会为它们分配不同且独立的内存地址。

相反,对于不可变对象int而言,内存中只存在一个值为1的对象。即使定义另一个变量c=1,它也会指向内存中同一个值为1的对象。换言之,不可变对象的地址是共享的。

接下来,让我们观察在函数中调用并修改可变对象与不可变对象时的情形。

首先看不可变对象int的最简单情况:

a=1
print(id(a))
def x(a):
  print(id(a))
  b=a
  print(id(b))
x(a)

运行结果如下:

1643643344
1643643344
1643643344

这看起来像是引用传递,函数外部的a、函数内部的a和b都指向了相同的内存地址。

但我们再考察一个极端情况:

a=1
print(id(a))
def x():
  b=1
  print(id(b))
x()

运行结果如下:

1643643344
1643643344

这是否令人惊讶?函数外部定义的a和函数内部定义的b看似毫无关联,但它们却指向了相同的内存地址!

那么,如何判断这究竟是值传递还是引用传递?讨论这个问题实际上没有意义,因为内存中只有一个值为1的对象。当我将值1传递给函数中的某个变量时,我实际上也传递了该地址,因为内存中仅存在这一个1。

甚至,我可以直接在函数内部给b赋值为1,这同样会使函数外部的a和函数内部的b指向同一个内存地址。

下面来看看传递可变对象list的情况:

l=[1,2,3]
print(id(l))
def a(x):
  print(id(x))
  x.pop()
  print(x)
  print(id(x))
  x=x+[3]
  print(x)
  print(id(x))
a(l)

运行结果如下:

883142451528
[1, 2]
[1, 2, 3]

可以看到,当我们将函数外部的列表L传递给函数后,变量x的地址与L一致,这看起来确实是引用传递,没有问题。

继续执行,当我们调用x自身的方法pop后,x变为[1, 2],且x的内存地址保持不变,这也在预期之中。

但是,当我们对x进行赋值操作后,x的内存地址发生了改变。

也就是说,只要创建了一个新的可变对象,Python就会分配一个新的内存地址。即使我们创建的新可变对象与已存在的旧可变对象内容完全一致,Python依然会分配一个新的地址(参见本文上半部分的“简单小实验”)。

而pop方法并非创建新的可变对象,它是对已有的可变对象进行修改。

因此可以总结为:

在Python中,不可变对象是共享地址的,而创建可变对象永远会分配新的内存地址。

此时再回头思考值传递和引用传递的问题,就会发现,在Python中讨论这一概念确实意义不大。

我们可以说:Python拥有一套独特的传参方式,这是由其作为动态语言的特性所决定的。

总结

以上就是本文关于深入理解Python中函数传递参数是值传递还是引用传递的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:Python实现一个简单的验证码程序、Python编程django实现同一个ip十分钟内只能注册一次、简单了解Python中的几种函数等,有什么问题可以随时留言,小编会及时回复大家的。感谢朋友们对本站的支持!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表