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

14 投票
4 回答
15658 浏览
提问于 2025-04-16 13:15

为什么在调用 sorting(L) 函数时,变量 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]

4 个回答

1

文档中特别提到,.sort()这个函数会直接改变原来的集合。如果你想遍历一个已经排序好的集合,可以使用sorted(L)。这个方法会提供一个生成器,而不是仅仅对列表进行排序。

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[:]

21

简单来说:Python 是通过值传递的,但传递的值其实是引用。实际的对象可以有从零到无数个引用指向它,而在修改这个对象时,谁拥有这个引用并不重要。

接下来一步一步分析你的例子:

  • L = [...] 这行代码在内存中创建了一个 list 对象,局部变量 L 存储了指向这个对象的引用。
  • sorting(严格来说,是指向全局名称 sorting 的可调用对象)被调用时,传入的是 L 存储的引用的一个副本,并把它存储在一个叫 x 的局部变量中。
  • 接着,x 中引用的对象的 sort 方法被调用。这个方法也得到了指向对象的引用(在 self 参数中)。它以某种方式修改了这个对象(是这个对象本身,而不是指向它的引用,后者只是一个内存地址)。
  • 现在,由于引用是被复制的,但指向的对象并没有被复制,所以我们讨论的其他引用仍然指向 同一个 对象。这个对象是在原地被修改的。
  • testScope 然后返回另一个指向这个列表对象的引用。
  • print 使用这个引用请求字符串表示(调用 __str__ 方法)并输出它。因为它仍然是同一个对象,所以当然会打印出排序后的列表。

所以每当你传递一个对象时,你就是在和接收它的人 共享 这个对象。函数可以(但通常不会)修改它们接收到的对象(通过调用修改方法或赋值成员)。不过要注意,赋值一个成员和赋值一个普通的名字是不同的——赋值一个名字只是修改了你的局部作用域,并不会影响调用者的对象。所以你不能修改调用者的局部变量(这就是为什么它不是通过引用传递的原因)。

进一步阅读:effbot.org 上关于为什么这不是通过引用传递的讨论,这与大多数人所说的通过值传递也不同。

撰写回答