【问题标题】:Are integers in Python passed by value or reference?Python中的整数是按值传递还是按引用传递?
【发布时间】:2013-10-24 07:32:25
【问题描述】:

我编写了以下代码来检查整数是按值传递还是按引用传递。

foo = 1

def f(bar):
    print id(foo) == id(bar)
    bar += 1
    print foo, bar

f(foo)

我得到的输出是

True
1, 2

在 Python 文档中,id(object) 返回对象的标识。在CPython 实现中,这是对象在内存中的地址。既然函数体第一条语句返回True,说明foo已经被引用传递了,但是为什么最后一条语句打印的是1, 2而不是2, 2呢?

【问题讨论】:

  • 在 Python 中,一切都是按值传递的。
  • 因为整数是不可变的。
  • @hcwhsa 整数不是按值传递的,因为它们是不可变的。 Python 中的整数(和所有其他值)总是按值传递。但是,您可以传递 references,这是不同的。换句话说,您可以按值传递引用。
  • @ajay f(foo) 实际上等同于bar = foo,即它创建了对同一对象的新引用,这就是为什么foo is barTrue。但是+= 对整数的操作(和所有操作)总是返回一个新的整数,即在bar += 1 之后,bar 将引用一个新对象,foo 仍然引用相同的旧对象,它仍然没有改变。

标签: python


【解决方案1】:

在 Python 中,就像在许多现代 OO 语言中一样

foo = 1

实际上创建了一个值为1 的对象,并为别名foo 分配了一个引用。 foo 的内部类型是PyIntObject。这意味着 Python 不使用 CPU / 硬件 int 类型,它总是使用对象在内部处理数字。正确的术语是“plain integer”,顺便说一句。

但是创建对象非常昂贵。这就是 Python 为一些数字保留内部缓存的原因。这意味着:

foo = 1
bar = 1
assert id(foo) == id(bar)

这不能保证,这只是实施的副作用。

Python 中的数字类型也是不可变的。因此,即使您的示例中的 bar 是缓存的 int 编号的别名,更改 bar 也不会修改内部值。相反,bar 指向另一个实例,这就是 id 更改的原因。

由于前面提到的优化,这也有效:

foo = 1
bar = 1
assert id(foo) == id(bar)

bar += 1
assert id(foo) != id(bar)

bar -= 1
assert id(foo) == id(bar)

【讨论】:

  • -1 小整数的 intern 很有趣,这也是不仔细查看id()s 的另一个原因,但与 OP 的 sn-p 完全无关。
  • 它会在使用时自动实习号码吗?换句话说,id(x) != id(x) 什么时候会变成False
  • @LasseV.Karlsen:我同意 delnen 的观点,你不能依赖 id() 的行为。一般来说,如果您觉得应该使用id(),那么您的设计可能已经损坏。
  • 这不相关,因为在 OP 的示例中没有从内部缓存中重用整数:1 出现一次(对它的引用被传递给函数,但没有尝试复制 1 并获取来自缓存的相同引用),2 出现一次。
  • @AaronDigulla 如果你没有实习,OP 程序的行为完全一样:foobar 一开始是一样的,+= 1 之后是不同的.没有foo = 1; bar = 1,而是一个(隐式)bar = foo。这使得关于实习的事情变得无关紧要。
【解决方案2】:

围绕这个“问题”似乎有很多困惑。 Python 中的变量名实际上都是对对象的引用。对变量名的赋值实际上并没有改变对象本身,而是将引用设置为新对象。所以在你的情况下:

foo = 1 #

def test(bar):
    # At this point, "bar" points to the same object as foo.
    bar = 2    # We're updating the name "bar" to point an object "int(2)".
    # 'foo' still points to its original object, "int(1)".
    print foo, bar # Therefore we're showing two different things.

test(foo)

Python 的语法类似于 C 的方式以及许多东西都是语法糖的事实可能会令人困惑。记住整数对象实际上是不可变的,foo += 1 可能是一个有效的语句似乎很奇怪。实际上,foo += 1实际上等价于foo = foo + 1,两者都转换为foo = foo.__add__(1),实际上返回一个新对象,如下所示:

>>> a = 1
>>> id (a)
18613048
>>> a += 1
>>> id(a)
18613024
>>>

【讨论】:

    【解决方案3】:

    会发生以下情况:

    print id(foo) == id(bar)
    

    身份相同。 print foo is bar 会产生相同的结果,顺便说一句。

    bar += 1
    

    这被翻译成:

    bar = bar.__iadd__(1)
    

    只有当这不起作用或不存在时,它才会调用:

    bar = bar.__add__(1)
    

    (我省略了bar = 1.__radd__(bar) 也可以被调用的情况。)

    由于bar 指的是一个不可变的数字,因此会返回一个不同的对象,因此bar 现在指的是2,而foo 保持不变。

    如果你这样做

    print id(foo) == id(bar)
    print foo is bar
    

    现在,您看到它们现在指向不同的对象。

    【讨论】:

      【解决方案4】:

      在 Python 2 中,当前实现 keeps an array of integer objects for all integers between -5 and 256。因此,如果您将一个变量分配为var1 = 1 并将其他变量分配为var2 = 1,它们都指向同一个对象。

      Python“变量”是指向对象的标签,而不是可以填充数据的容器,因此在重新分配时,您的标签指向一个新对象(而不是包含新数据的原始对象)。请参阅堆栈溢出问题Python identity: Multiple personality disorder, need code shrink

      在您的代码中,我引入了另外几个打印语句,它们将显示变量是按值传递的

      foo = 1
      def f(bar):
          print id(foo) == id(bar)
          print id(1), id(foo), id(bar) #All three are same
          print foo, bar 
          bar += 1
          print id(bar), id(2)
          print foo, bar
      
      f(foo)
      

      【讨论】:

      • 这很好地解释了实习,在这种情况下根本不相关。调用时隐含的bar = foo 也会发生在非实习号码中,例如123432
      猜你喜欢
      • 2016-12-18
      • 2016-11-28
      • 2017-11-14
      相关资源
      最近更新 更多