【问题标题】:Why changing object type affects Python's behavior on pass-by-reference?为什么更改对象类型会影响 Python 在传递引用时的行为?
【发布时间】:2017-08-02 06:06:54
【问题描述】:

我会期待以下代码:

def a(l):
    l.append(3)

def b(l):
    l = 5

def c(l):
    l = (2,-2)

numbers = [1,2,3,4,5]
a(numbers)
print(numbers)
b(numbers)
print(numbers)
c(numbers)
print(numbers)

打印:

[1, 2, 3, 4, 5, 3]
5
(2,-2)

但它会打印:

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

为什么??

编辑:

这是因为引用传递共享调用的区别!

【问题讨论】:

  • 也许您将 pass-by-reference 与传递 as 引用的对象混淆(即在 Python 中,对象传递 as 引用)?
  • 在第一种情况下,您传入一个可变对象并对其进行变异。在其他情况下,您将本地名称更改为指向新值并且这不会更改调用范围,您需要 return l 将该值返回到调用范围,例如numbers = b(numbers)
  • @AChampion 它仍然不会更新原始的l 变量。这需要将返回值分配给(全局)变量。
  • @FelixKling 我认为您是对的,请您指定通过引用传递和作为参考传递之间的差异吗?

标签: python pass-by-reference


【解决方案1】:

我的看法是,Python 与 C 和 Java 一样,按值传递。对于您的a() 函数,您通过值传递引用。那里没有引用传递

另一方面,Perl 确实 通过别名传递,这与 通过引用传递有一些相似的行为。

我看到一位计算机科学教授错误地声称 Objective-C 通过引用传递,因为教科书上是这么说的,但没有经过深思熟虑。但它仍然只是按值传递。

【讨论】:

    【解决方案2】:

    第一种方法(a)实际上是修改通过l.append(...)传递的列表项

    第二种和第三种方法(bc重新分配参数l 到一个新值。这种重新分配是函数本地的,不会返回,因此会丢失。当您在执行b(numbers)c(numbers) 之后打印numbers 时,您正在打印由方法a 修改的值。

    【讨论】:

      【解决方案3】:

      Python 中的大多数值都是不可变的,这意味着它们无法更新。这意味着变量的值不能改变。但是,可以将引用指向一个新的(不可变的)值。因此,在写作时

      i = 4
      i = 5
      

      实际上,您首先让i 指向(不可变值)4,然后将i 指向(同样不可变)值5。

      然而,列表是可变的。这意味着我们可以做到

      L = [3,4]
      L.append(5)
      

      并使用新值 [3, 4, 5] 更新相同的引用对象。

      调用函数时,例如的值iL 不会直接传递给函数。而是传递对值的引用。原始引用也保存在调用环境中,待被调用函数返回时恢复。

      查看您的功能意味着:

      def a(l):
          l.append(3)
      

      这里,l 的引用没有改变/更新,因为 append 将改变引用的值。因此,当从函数 l 返回时,将“恢复”到函数中使用的相同值,并更新列表。

      def b(l):
          l = 5
      
      def c(l):
         l = (2,-2)
      

      然而,在这两个函数中,由于赋值,引用 l 指向新的东西。因此我们修改了引用本身。然后,当返回时,引用恢复为指向调用范围中使用的值。因此,l 将再次成为原始列表,并且函数中使用的引用值将超出范围,并且该引用指向的任何值都将丢失/删除。

      因此,这不仅仅是一个语义差异,而不是 通过引用调用作为引用调用通过共享调用具有类型的不变性以及引用在 Python 中的工作方式。

      【讨论】:

      • 很好的解释。但是请注意,l 是否在第一个函数 a 中重新分配并不重要。 numbers 将使用此修改后的函数:def a(l): l.append(3); l = 0
      • @Alexander(和其他人)是的,你是对的。它与引用相同有关,def a(l): l=[] l.append(3) 不会更新原始的l,因为我们首先分配l 指向一个新变量。
      • "调用函数时,参数(例如 i 和 L)不会作为值传递给函数。而是传递对参数的引用。但是,原始引用保存在调用环境中, 在被调用函数返回时恢复。"不。参数传递。参数 引用。 Python 中的每个值(每个变量的值和每个表达式的结果)都是一个引用。没有“保存”或“恢复”任何东西。
      • @newacct 我可以告诉你参数 are 作为语义上的引用,但我想争辩说,在调用函数时,当前环境是变量,确实存储在堆栈中。它们仍然可以从调用函数访问,但它们仍然被存储。然后,当从函数返回时,出栈,这将恢复变量的原始值。
      • @JohanL:被调用函数接收传递值的副本。传递的参数是被调用函数中的局部变量,并且存在于被调用函数的堆栈帧中。调用函数不能访问它们。调用函数自己的变量在自己的栈帧中,其他任何函数都无法访问。
      猜你喜欢
      • 2018-03-04
      • 2015-02-25
      • 1970-01-01
      • 2020-09-22
      • 2020-08-09
      • 2013-06-26
      • 1970-01-01
      • 2016-01-24
      • 1970-01-01
      相关资源
      最近更新 更多