Python变量作用域(传引用还是复制?)

14

为什么变量L在调用sorting(L)函数时被改变?在其他语言中,L的副本会被传递给sorting()函数,以便对x的任何更改都不会改变原始变量。

def sorting(x):
    A = x #Passed by reference?
    A.sort() 

def testScope(): 
    L = [5,4,3,2,1]
    sorting(L) #Passed by reference?
    return L

>>> print testScope()

>>> [1, 2, 3, 4, 5]

我知道sort()会改变集合,但为什么它会改变在testScope()中定义的原始L呢?听起来像是将L的引用传递给了排序函数?对吗?这是正确的说法吗? - Ryan
@lunixbochs:不,不,不!请停止暗示像Python、Java、C#(在使用引用类型时)等语言使用按引用传递。这是完全错误的。 - user395760
4个回答

21
长话短说:Python使用传值方式,但传递的是引用。实际对象有0到无限个引用指向它们,对于改变该对象,不管你是谁以及如何获取该对象的引用都没有影响。
逐步分析您的示例:
  • L = [...] 在内存中创建了一个 list 对象,本地变量 L 存储对该对象的引用。
  • sorting(严格来说,是由全局名称 sorting 指向的可调用对象)使用存储在 L 中的引用的副本进行调用,并将其存储在名为 x 的本地变量中。
  • 调用指向 x 中包含的引用的对象的 sort 方法。它也会得到一个对对象的引用(在 self 参数中)。它以某种方式改变了该对象(该对象,而不是指向该对象的某个引用,后者仅仅是一个内存地址)。
  • 现在,由于复制了引用,但没有复制引用所指向的对象,因此我们讨论的所有其他引用仍然指向同一个对象。这个被“原地”修改的唯一对象。
  • testScope 然后返回对该列表对象的另一个引用。
  • print 使用它来请求字符串表示形式(调用 __str__ 方法)并输出它。由于它仍然是同一个对象,当然它会打印已排序的列表。
无论何时你将一个对象传递给任何地方,你会与接收它的人分享它。函数可以(但通常不会)改变传递给它们的对象(由引用指向的对象),从调用可变方法到分配成员。但请注意,分配成员与分配普通的名称是不同的——这仅意味着改变你的本地作用域,并不影响调用者的任何对象。所以你不能改变调用者的局部变量(这就是为什么它不是按引用传递的原因)。
进一步阅读:effbot.org上的讨论 为什么它不是按引用传递,而且不是大多数人称之为按值传递的方式。

只有可变对象可以被改变,而不是所有的对象。字符串是不可变的。你只能改变一个变量的绑定,使得该名称引用不同的对象,但你不能改变一个不可变的对象本身。不可变对象没有这样的方法,可以改变它们的值。列表被称为可变对象,这意味着它具有可以改变其值的方法或操作。在Python中,名称绑定是完全独立于可变性的东西。 - n611x007
@naxa,关于这个答案,你的观点是什么?绑定名称确实与改变对象完全不同,答案也是这样说的。它提到两者的原因是为了指出它们的不同之处。指出这一点的原因是,几乎所有初学者对参数传递和可变性的困惑都来自于他们没有意识到这种区别。 - user395760
抱歉!重点是:您在此处有一个不正确或误导性的句子:“实际对象具有0到无限个引用,并且每个引用都可以对其进行突变。”因为仅通过引用就不能突变对象。它需要可变。我认为让初学者理解比正确性更重要,但我也认为提供错误的假设同样是一个坏主意。问题很难修复。但最好修复。如果我有替代方案,我应该让您知道。关闭:您是否愿意删除我们之前的评论以使其不那么混乱? - n611x007
@naxa 我明白了。但我想不出更好的表达方式。可以说“尝试突变”,但这会引发一个问题,即它可能会失败,这取决于引用来自何处。可以将不可变对象豁免为特殊情况,但这似乎走上了荒谬的“编程语言对不可变对象进行了特殊处理”的道路。我倾向于保持原样,并将其视为真空真理:所有突变不可变对象的代码(即没有)都可以这样做,无论它从哪里获取对该对象的引用。我认为这比我能想到的所有其他选择都不会误导人。 - user395760
@naxa 实际上,我觉得我找到了更好的措辞,请看一下。 - user395760
我同意,这样做简单明了,而且是正确的。不错的发现! - n611x007

11

Python有可变和不可变对象的概念。像字符串或整数这样的对象是不可变的——您对其进行的每个更改都会创建一个新的字符串或整数。

列表是可变的,可以原地操作。请参见以下内容。

a = [1, 2, 3]
b = [1, 2, 3]
c = a

print a is b, a is c
# False True

print a, b, c
# [1, 2, 3] [1, 2, 3] [1, 2, 3]
a.reverse()
print a, b, c
# [3, 2, 1] [1, 2, 3] [3, 2, 1]
print a is b, a is c
# False True

请注意c被反转了,因为c "是" a。有许多方法可以将列表复制到新的内存对象中。一种简单的方法是切片:c = a[:]


1
文档中明确提到.sort()函数会改变集合本身。如果你想迭代一个排序后的集合,应该使用sorted(L)。这将提供一个生成器而不仅仅是对列表进行排序。

0
a = 1
b = a
a = 2
print b

引用并不等同于独立的对象。

.sort() 方法也会改变集合本身。


那么,您的意思是内存中的对象“1”仍然存在,并且仍然由变量b指向? - Ryan

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