在Python中传递值

80

当你在Python中将像列表、数组这样的集合传递给另一个函数时,它是制作副本还是只是指针?


18
这个问题是在2009年2月提出的,而“原始”的问题是在同年6月提出的。为什么这个被认为是重复的问题? - Noob Saibot
5
在“重复”的答案中,回答得要好得多。 - Makoto
1
Mark Ransom的这个答案和effbot关于Python对象的博客一起,将会让事情变得尽可能清晰明了。 - akki
2
@Makoto,这并不能证明第一个问题被标记为重复。这会鼓励回答的重复。 - cellepo
1
@cellepo:我的评论也被删除了...版主做得太好了,审查制度太严格了! - Joan Venge
这篇博客文章已经无法访问了。感谢 Stack Overflow 存储了所有的内容。 - esboych
8个回答

102

Python以传值方式传递对象的引用(像Java一样),而且Python中的所有东西都是对象。这听起来很简单,但你会注意到有些数据类型似乎表现出了传值的特性,而另一些似乎像是传引用……怎么回事呢?

理解可变和不可变对象很重要。一些对象,比如字符串、元组和数字,是不可变的。在函数/方法内部修改它们将创建一个新实例,而函数/方法外部的原始实例不会被改变。另一些对象,比如列表和字典,是可变的,这意味着你可以就地更改对象。因此,在函数/方法内部修改对象也会改变函数/方法外部的原始对象。


19
你先说“Python按引用传递”,但是在引用的文本中,它说“Python按值传递对象的引用”——这不是同一件事!实际上,Python根本不是按引用调用的,它是共享调用 - Daniel Pryden
14
Python不是按照通常理解的引用传递(例如Pascal或C++)。给变量赋值不会以任何方式影响调用者,而且无论涉及的数据类型如何都是如此。在调用函数时,Python为相同的对象创建一个新名称,因此对对象进行更改将反映在调用者中,但对函数局部变量进行赋值则不会反映。这与Java或Lisp中的机制完全相同。你的答案很遗憾地增加了混淆。 - user4815162342
@user4815162342:只是为了明确,将值分配给函数中的局部变量会创建一个新对象,该对象在函数执行期间存在,并在函数退出时消失。正确吗? - vipulnj
@vipulnj 正确。除非对象存储在全局变量中,否则它将不会在函数退出后存在 - 但它仍然不会影响调用者所看到的对象。 - user4815162342

84

事实上,整个引用和值的概念并不适用于Python。Python没有变量的“值”,只有对象和指向对象的名称。

因此,当你调用一个函数并在括号中放入一个“名称”时,像这样:

def func(x): # defines a function that takes an argument
    ... # do something here

func(myname) # calling the function

传递的是myname指向的实际对象,而不是myname 本身。在函数内部,给定另一个名称x来引用传递的同一对象。

如果可变,您可以在函数内部修改对象,但无法更改外部名称所指向的内容。当执行以下操作时同样会发生这种情况:

anothername = myname
因此,我可以回答你的问题:它是"按值传递",但所有的值都只是对象的引用。

我已经决定下次被要求解释时,我将使用id()函数来展示名称与引用的绑定关系。 - Tim Richardson
1
名称是当前作用域中的引用。 "名称"和"变量"(如Java中)之间唯一可见的区别是当您查看locals()时,这通常是不必要的。是的,Java有几种原始类型,而Python只有一种。 - Elazar
1
那句话完美地概括了我看到许多Python新手非常困惑的内容! - Tomerikoo

28

这里的回答很有帮助,但我觉得需要展示这个细微的区别,我没有看到过相关的涵盖,我通过后续的CL实验证明了这一点:

  1. 一个不可变对象在函数调用中单独不能被改变。 (至今为止的回答都已经说了这一点...)
  2. 但是,在可变对象中包含的不可变对象可以在方法调用中被重新赋值。

这里的“num”没有改变,因为它是一个不可变的数字对象[支持我的第一点]:

>>> def incr_num(num):
        num += 1

>>> num = 0

>>> num
0

>>> incr_num(num)

>>> num
0

list[0]这里也是一个不可变的数值对象。

>>> def incr_list(list):
        list[0] += 1

>>> list = [0]

>>> list[0]
0

>>> incr_list(list)

>>> list[0]
1

那么,作为一个不可变的数字对象,list[0] 是如何改变(支持我的第二点)而上面例子中的数字对象“num”没有改变呢? 不可变的数字对象list[0]被包含在可变的列表对象“list”中,而第一个例子中的“num”只是一个非包含的数字对象(不可变的)。

虽然出于好意,我认为@Stephen Pape的最高评分答案(如下所引用)以及其他类似的答案并不完全正确(这促使我撰写了这篇答案):

某些对象,如字符串、元组和数字,是不可变的。在函数/方法内部修改它们将创建一个新实例,函数/方法外部的原始实例不会改变。

我上面的第二个代码实验展示了在方法内部更改数字对象(“list[0]”),然后函数外部的原始实例也被更改了。


1
这是之前回答的更长版本https://dev59.com/3nRB5IYBdhLWcg3wuZbo#534389,值得发布。 - sancho.s ReinstateMonicaCellio
@cellepo,您的回答解释了行为,但并没有解释这种行为的原因。我的意思是,当作为参数传递时,列表中的数字与直接传递的整数之间有什么区别。为什么恰好一个整数会改变而另一个不会? - IqbalHamid
@IqbalHamid,为什么你对我不像对其他回答的评论那样友善和尊重?为什么你不向他们提出你的问题呢?我甚至提供了比他们更多的解释,而你却要求我提供更多的解释... - cellepo
@celleppo。没有任何不敬之意。您提出了一个非常有趣的观察结果,这是Python程序员需要注意的。因此,感谢您引起我们的关注。但是,我最初阅读您的答案时并不清楚为什么Python会出现这种行为。您接着强调了两种情况之间的区别。但我无法理解列表是什么使其可变。另一个人的代码与您的代码相同,但返回引用提供了额外的清晰度,以便了解正在发生的事情以及如何进行操作。没有任何不敬之意。您的回答仍然很有帮助。 - IqbalHamid
我从未声称要将任何推理解释到迎合你个人的水平;问题并没有要求这样做。但是我的免费答案已经对推理进行了解释 - 这就是在我的答案顶部以粗体存在的内容 - 你可以在编辑历史记录中看到该部分自你第一次评论之前就没有改变,这显示出你缺乏应有的尽职调查:仔细阅读,并研究自己找到符合你个人需求的答案 - 至少在批评我的免费建议之前尝试一下这样做(我的免费建议已经做到了你所要求的)。 - cellepo
这表明了你的立场,你现在已经删除了在另一个答案下的先前评论。 - cellepo

9

虽然传递的是引用,但如果参数是一个不可变对象,在方法内部修改它将创建一个新实例。


4
对象被传递,不是复制,而是引用底层对象。

然而,你也需要知道Python何时创建新对象以及何时取消引用。 - Tim Richardson

3

我还建议您查看copy模块:

Python的copy文档

它将帮助您了解底层问题以及如何使用它来执行自己的深度复制。


3

通过引用:

>>> x = [0,1,2,3]
>>> def foo(x_list):
    x_list[0] = 1


>>> foo(x)
>>> x
[1, 1, 2, 3]

@Harper Shelby:不错,但是有风险的例子。对于像字符串、元组、整数等不可变对象无效。 - S.Lott
是的,但并不是因为不可变对象被按值传递,而是因为它们在变异时会自动复制。 - Josh Heitzman

1

请允许我举一个简单的例子

def swap(a, b):
    x = a
    print id(x)
    print id(a)
    print id(b)
    a = b

    print id(a)
    b = x
    print id(b)
    a[0]= '20'




var1 = ['1','2','3','4']
var2 = ['5','6','7','8','9']
print id(var1)
print id(var2)

swap(var1, var2)

print id(var1)
print id(var2)
print var1
print var2

产生以下结果

28329344 var1 28331264 var2 28329344 x 28329344 a 28331264 b a = b 后 28331264 a b = x 后 28329344 b 返回后 28329344 var1 28331264 var2 ['1', '2', '3', '4'] ['20', '6', '7', '8', '9']

映射到内存地址 28329344 28331264 var1 var2 a b x a=b 后 a b=x 后 b a[0] = '20' 后 [0] = '20' 返回后 ['1','2','3','4'] ['20', '6', '7', '8', '9']


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接