【问题标题】:What happens when you assign the value of one variable to another variable in Python?当你在 Python 中将一个变量的值赋给另一个变量时会发生什么?
【发布时间】:2017-07-12 09:15:48
【问题描述】:

这是我学习python的第二天(我知道C++的基础知识和一些OOP。),我对python中的变量有些困惑。

这是我目前对它们的理解:

Python 变量是对对象(可变或不可变)的引用(或指针?)。当我们有类似num = 5 的东西时,不可变对象5 会在内存中的某处创建,名称-对象引用对num 会在某个命名空间中创建。当我们有 a = num 时,不会复制任何内容,但现在两个变量都引用同一个对象,并且 a 被添加到同一个命名空间。

这就是我的书,用 Python 自动化无聊的东西,让我感到困惑。由于是新手书,没有提及对象、命名空间等,并尝试解释如下代码:

>>> spam = 42
>>> cheese = spam
>>> spam = 100
>>> spam
100
>>> cheese
42

它提供的解释与 C++ 书籍中的解释完全相同,因为我们正在处理指向对象的引用/指针,所以我对此并不满意。所以在这种情况下,我猜在第 3 行中,由于整数是不可变的,spam 被分配了一个全新的指针/引用,指向内存中的不同位置,即它最初指向的内存没有被修改.因此,我们有cheese 引用spam 引用的初始对象。这是正确的解释吗?

【问题讨论】:

  • 您将42 存储在spam 中,现在您将spam 存储在奶酪中意味着cheese = 42,之后您将spam 替换为100,您没有编辑@987654338 @ 这就是为什么 cheese 仍然是 42
  • 正如乔纳斯在他的回答中指出的那样,不变性在这里是一条红鲱鱼:如果你使用像列表这样的可变对象,你会得到完全相同的结果。关键是您将变量重新分配到一个新的引用。
  • @mini:我更喜欢将 Python 变量视为标签而不是存储桶。您将标签spam 贴在数字42 上。接下来,您将标签cheese 贴在标记为spam 的东西上(请注意,不是在标签本身的顶部)。然后你把spam标签从上面撕下来,放在数字100上。

标签: python variables object


【解决方案1】:

作为 C++ 开发人员,您可以将 Python 变量视为指针。

因此,当您编写spam = 100 时,这意味着您将先前指向对象42 的指针“分配”为指向对象100

早些时候,cheese 被分配指向与 spam 指向的对象相同的对象,当时恰好是 42。由于你没有修改cheese,它仍然指向42

在这种情况下,不变性与它无关,因为指针分配不会改变所指向对象的任何内容。

【讨论】:

  • 同js中的对象
  • 不变性很重要,因为这意味着您可以安全地将引用视为一个值。将可变对象当作值来处理是有风险的。
【解决方案2】:

您可以或多或少地将变量用作指针是正确的。然而,示例代码将极大地帮助解释 这实际上是如何工作的。

首先,我们将大量使用id 函数:

返回对象的“身份”。这是一个整数,保证在其生命周期内对于该对象是唯一且恒定的。具有不重叠生命周期的两个对象可能具有相同的 id() 值。

这可能会在您的机器上返回不同的绝对值。

考虑这个例子:

>>> foo = 'a string'
>>> id(foo) 
4565302640
>>> bar = 'a different string'
>>> id(bar)
4565321816
>>> bar = foo
>>> id(bar) == id(foo)
True
>>> id(bar)
4565302640

你可以看到:

  • 原来的 foo/bar 有不同的 id,因为它们指向不同的对象
  • 当 bar 分配给 foo 时,它们的 id 现在是相同的。这类似于它们都指向您在创建 C++ 指针时看到的内存中的相同位置

当我们改变 foo 的值时,它被分配到不同的 id:

>>> foo = 42
>>> id(foo)
4561661488
>>> foo = 'oh no'
>>> id(foo)
4565257832

还有一个有趣的观察结果是整数隐含地具有此功能,最高可达 256:

>>> a = 100
>>> b = 100
>>> c = 100
>>> id(a) == id(b) == id(c)
True

但是超过 256 后就不再适用了:

>>> a = 256
>>> b = 256
>>> id(a) == id(b)
True
>>> a = 257
>>> b = 257
>>> id(a) == id(b)
False

但是,将a 分配给b 确实会保持 id 与之前显示的相同:

>>> a = b
>>> id(a) == id(b)
True

【讨论】:

    【解决方案3】:

    在我看来,对一种语言有不同的看法。

    • “语言律师”视角。
    • “实用程序员”的观点。
    • “实施者”视角。

    从语言律师的角度来看,python 变量总是“指向”一个对象。然而,与 Java 和 C++ 不同,== = 等的行为取决于变量指向的对象的运行时类型。此外,在 python 中,内存管理是由语言处理的。

    从实际程序员的角度来看,我们可以将整数、字符串、元组等是不可变*对象而不是直接值这一事实视为无关紧要的细节。例外情况是,当存储大量数值数据时,我们可能希望使用可以直接存储值的类型(例如 numpy 数组),而不是最终会包含对微小对象的引用的数组。

    从实现者的角度来看,大多数语言都有某种似是而非的规则,如果指定的行为是正确的,那么无论事情是如何在幕后实际完成的,实现都是正确的。

    所以是的,从语言律师的角度来看,您的解释是正确的。从实际程序员的角度来看,您的书是正确的。实现的实际作用取决于实现。在 cpython 中,整数是真实对象,尽管小值整数是从缓存池中获取的,而不是重新创建的。我不确定其他实现(例如 pypy 和 jython)做了什么。

    * 请注意此处可变对象和不可变对象之间的区别。对于可变对象,我们必须小心对待它“像一个值”,因为其他一些代码可能会改变它。对于不可变对象,我们没有这样的顾虑。

    【讨论】:

      【解决方案4】:

      Python 既不是按引用传递,也不是按值传递。 Python 变量不是指针,它们不是引用,它们不是值。 Python variables are names.

      如果您需要相同的短语类型,可以将其视为“pass-by-alias”,或者可能是“pass-by-object”,因为您可以从指示它的任何变量中改变相同的对象,如果它是可变的,但是重新分配一个变量(别名)只会改变那个变量。

      如果有帮助:C 变量是您将值写入其中的框。 Python 名称是你放在值上的标签。

      Python 变量的名称是全局(或本地)命名空间中的一个键,它实际上是一个字典。基础值是内存中的某个对象。赋值给那个对象一个名字。将一个变量分配给另一个变量意味着两个变量都是同一个对象的名称。重新分配一个变量会更改该变量命名的对象,而不会更改另一个变量。您已移动标签,但未更改之前的对象或上面的任何其他标签。

      在 CPython 实现的底层 C 代码中,每个 Python 对象都是一个PyObject*,所以如果你只有指向数据的指针(没有指向指针的指针,没有直接指向的指针),你可以认为它像 C 一样工作-传递的值)。

      您可以说 Python 是按值传递,其中值是指针……或者您可以说 Python 是按引用传递,其中引用是副本。

      【讨论】:

      • 将其称为“按名称传递”的问题在于,已经有一个称为“按名称调用”的参数传递约定,其含义完全不同。在按名称调用中,每次函数使用参数时都会计算参数表达式,如果函数不使用参数则永远不会计算。
      【解决方案5】:

      当您运行spam = 100时,python 在内存中再创建一个对象,但不会更改现有对象。所以你仍然有指针 cheese 到 42 和 spam 到 100

      【讨论】:

        【解决方案6】:

        spam = 100 行中发生的事情是用另一个指向另一个对象的指针(类型 int,值 100)替换先前的值(指向类型为 int 的对象的指针,值 42

        【讨论】:

        • 整数是栈上的值对象,对吧?
        • 是的,它们类似于您在 C++ 中使用 new Class() 语法创建的对象。此外,在 Python 中,anythingobject 类/子类的实例。
        • @GertKommer 至少在 CPython 中,所有对象都存在于堆上。没有“价值对象”的区别。只有对象,一切都是对象。这就是为什么典型的 int 大小约为 28 字节的原因,具体取决于 Python 版本,因为它具有整个 Py_Object 开销。 Small-ints 被缓存为 CPython 优化。
        【解决方案7】:

        正如@DeepSpace 在 cmets 中提到的,Ned Batchelder 在博客中出色地揭开了变量(名称)和赋值的神秘面纱,他在 2015 年 PyCon 上发表了演讲,Facts and Myths about Python names and values。对于任何掌握水平的 Python 爱好者来说,它都可以提供深刻的见解。

        【讨论】:

          【解决方案8】:

          在 Python 中,variable 保存对 objectreferenceobject 是一块分配的内存,其中包含一个值和一个 header。对象的标头包含其类型和一个引用计数器,该计数器表示该对象在源代码中被引用的次数,以便垃圾收集可以识别是否可以收集对象。

          现在,当您为变量赋值时,Python 实际上会将 references 分配给分配给对象的内存位置的指针

          # x holds a reference to the memory location allocated for  
          # the object(type=string, value="Hello World", refCounter=1)
          
          x = "Hello World" 
          

          现在,当您将不同类型的对象分配给同一个变量时,您实际上更改了引用,使其指向不同的对象(即不同的内存位置)。当您为变量分配不同的引用(以及对象)时,垃圾收集器将立即回收分配给前一个对象的空间,假设源代码中没有任何其他变量引用它:

          # x holds a reference to the memory location allocated for  
          # the object(type=string, value="Hello World", refCounter=1)
          
          x = "Hello World" 
          
          # Now x holds the reference to a different object(type=int, value=10, refCounter=1)
          # and object(type=string, value="Hello World", refCounter=0) -which is not refereced elsewhere
          # will now be garbage-collected.
          x = 10
          

          现在来看你的例子,

          spam 持有对 object(type=int, value=42, refCounter=1) 的引用:

          >>> spam = 42
          

          现在cheese 也将持有对对象的引用(type=int, value=42, refCounter=2)

          >>> cheese = spam
          

          现在垃圾邮件包含对不同对象的引用(type=int, value=100, refCounter=1)

          >>> spam = 100
          >>> spam
          100
          

          但是奶酪会一直指向对象(type=int, value=42, refCounter=1)

          >>> cheese
          42
          

          【讨论】:

            【解决方案9】:

            当您存储 spam = 42 时,它会在内存中创建一个对象。然后你分配cheese = spam,它将spam引用的对象分配给cheese。最后,当您更改spam = 100 时,它只会更改spam 对象。所以cheese = 42

            【讨论】:

            • "然后你分配 cheese = spam ,它会在内存中创建另一个对象 " 不,它没有。它将spam 引用的对象分配给cheese。没有创建新对象。
            【解决方案10】:

            numpy.copy()函数页面有解释

            https://docs.scipy.org/doc/numpy/reference/generated/numpy.copy.html

            它给出的例子如下:

            创建一个数组 x,带有一个引用 y 和一个副本 z:

            x = np.array([1, 2, 3])
            y = x
            z = np.copy(x)
            

            注意,当我们修改 x 时,y 会改变,但 z 不会改变:

            x[0] = 10
            x[0] == y[0]
            True
            x[0] == z[0]
            False
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2023-01-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-02-11
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多