【问题标题】:Python Variable Scope (passing by reference or copy?)Python 变量范围(通过引用或复制传递?)
【发布时间】:2011-07-11 04:27:30
【问题描述】:

为什么变量 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。听起来 sort(L) 正在将 L 的引用传递给排序函数?正确的?这是一个正确的说法吗?
  • @lunixbochs:不,不,不!请停止暗示 Python、Java、C#(使用引用类型时)等语言使用 pass-by-reference。这是完全错误的。

标签: python scope


【解决方案1】:

长话短说:Python 使用按值传递,但按值传递的东西是引用。实际的对象有 0 到无穷大的引用指向它们,为了改变该对象,你是谁以及你如何获得对该对象的引用并不重要。

逐步浏览您的示例:

  • L = [...] 在内存某处创建一个list 对象,局部变量L 存储对该对象的引用。
  • sorting(严格来说,指向全局名称sorting的可调用对象)使用L存储的引用副本调用,并将其存储在名为x的本地中。
  • 调用x中包含的引用所指向的对象的方法sort。它还获得对对象的引用(在self 参数中)。它以某种方式改变了该对象(对象,而不是对该对象的一些引用,这不仅仅是一个内存地址)。
  • 现在,由于引用被复制,但不是引用指向的对象,我们讨论的所有其他引用仍然指向相同的对象。 “就地”修改的一个对象。
  • testScope 然后返回对该列表对象的另一个引用。
  • print 使用它来请求字符串表示(调用__str__ 方法)并输出它。因为它仍然是同一个对象,所以它当然是打印排序列表。

因此,无论何时您将对象传递到任何地方,您与接收它的人共享它。函数可以(但通常不会)改变它们传递的对象(由引用指向),从调用变异方法到分配成员。请注意,分配成员与分配普通的 ol' 名称不同 - 这仅意味着改变您的本地范围,而不是任何调用者的对象。所以你不能改变调用者的本地人(这就是它不是通过引用传递的原因)。

进一步阅读:A discussion on effbot.org 为什么它不是按引用传递,也不是大多数人所说的按值传递。

【讨论】:

  • 只有 mutable 对象可以被变异,而不是全部。一个字符串是immutable。您只能更改变量name's binding,因此名称指的是不同的对象,但您不能更改不可变对象本身。不可变对象没有这样的方法可以改变它们的值。列表称为 mutable 对象,这意味着它具有可以更改其值的方法或操作。 名称绑定与Python中的mutability完全不同。
  • @naxa 您对这个答案的看法究竟是什么?绑定名称确实与变异对象完全分开,答案也是如此。它提到两者的原因是指出它们是不同的。指出这一点的原因是,几乎所有初学者都对参数传递和可变性感到困惑,因为他们没有意识到这种区别。
  • 对不起!关键是:您在这里有一个不正确或误导性的句子:The actual objects have 0 to infinity references and everyone with a reference can mutate them.,因为您无法仅通过引用它来改变对象。它需要可变的。我认为让初学者理解比正确更重要,但我认为给出错误的假设同样是个坏主意。这件事很难解决。但最好固定。如果我有其他选择,我应该让你知道。 Off:您可以移除我们的 prev cmets 以保持整洁吗?
  • @naxa 我明白了。但我想不出更好的表达方式。可以说“尝试变异”,但这提出了一个问题,即它是否可能会失败,具体取决于引用的来源。可以将不可变对象作为一种特殊情况排除在外,但这似乎走上了荒谬的“语言对不可变对象的处理方式不同”的道路。我倾向于留下它并将其视为空洞的事实:所有改变不可变对象(即没有)的代码都可以这样做,无论它从何处获得对该对象的引用。我认为这比我能想到的所有替代方案都少误导。
  • @naxa 其实我觉得我找到了更好的措辞,看看吧。
【解决方案2】:

Python 有 Mutable 和 Immutable objects 的概念。像字符串或整数这样的对象是不可变的 - 您所做的每一次更改都会创建一个新的字符串或整数。

列表是可变的,可以就地操作。见下文。

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

【讨论】:

    【解决方案3】:

    在文档中特别提到了.sort() 函数会改变集合。如果您想遍历排序的集合,请改用sorted(L)。这提供了一个生成器,而不仅仅是对列表进行排序。

    【讨论】:

      【解决方案4】:
      a = 1
      b = a
      a = 2
      print b
      

      引用与单独的对象不同。

      .sort() 也会改变集合。

      【讨论】:

      • 那么,你是说内存“1”中的对象仍然存在并且仍然被变量b指向?
      猜你喜欢
      • 2011-09-01
      • 1970-01-01
      • 2012-03-30
      • 2020-11-08
      • 2013-09-10
      • 2017-03-27
      • 2018-03-17
      • 2013-11-21
      • 1970-01-01
      相关资源
      最近更新 更多