【问题标题】:Why variable = object doesn't work like variable = number为什么变量 = 对象不像变量 = 数字那样工作
【发布时间】:2015-07-07 17:18:01
【问题描述】:

这些变量赋值按我的预期工作:

>>> a = 3
>>> b = a
>>> print(a, b)
(3, 3)
>>> b=4
>>> print(a, b)
(3, 4)

但是,这些分配的行为不同:

>>> class number():
...     def __init__(self, name, number):
...         self.name = name
...         self.number = number
... 
>>> c = number("one", 1)
>>> d = c
>>> print(c.number, d.number)
(1, 1)
>>> d.number = 2
>>> print(c.number, d.number)
(2, 2)

为什么cd 相同,而不是(a, b) 示例?我怎样才能在(a, b) 中的(c, d) 类示例中执行类似操作?即复制对象,然后更改其中的一部分(不会影响我借用属性的对象)?

【问题讨论】:

  • 当您的变量是可变类型时,这与variable = variable 完全一样。用列表试试。
  • 打印类,你会看到类似:<__main__.number object at 0x7f455b87c7b8> <__main__.number object at 0x7f455b87c7b8> ... 注意相同的地址 :-) 所以你设置了一个引用,而不是复制对象。
  • 您不会对“variable = object”与“variable = number”感到困惑 - 您对“variable = thing”与“variable.field = thing”感到困惑。
  • 这让我想起了我发布的另一个问题的答案,这应该能对此有所了解:stackoverflow.com/questions/3059395/…

标签: python python-2.7 variables object python-3.x


【解决方案1】:

为什么c 与 d 相同,与 (a, b) 示例不同?

在 python 中,变量不是对象。
“哈姆雷特不是莎士比亚写的;它只是一个叫莎士比亚的人写的。” Python 在事物和我们用来指代该事物的标签之间做出了至关重要的区分。 “那个叫莎士比亚的人”是一个男人。 “莎士比亚”只是一个名字。如果我们这样做:

l = []

那么[] 是空列表。 l 是指向空列表的变量,但l 本身并不是空列表。

因此,在 python 中,变量更像是一个标签。当你在 Python 中进行赋值时,它会用变量名标记值。

a = 1

如果您更改变量的值,它只会将标记更改为内存中的新值。

将一个变量分配给另一个变量会使新标签绑定到相同的值,如下所示。

b = a  

如果您将新值 3 分配给 b,那么它将绑定到该新值,而 a 仍将绑定到 2

在第二个 sn-p 中,d = c 将使 cd 指向同一个对象。修改任何对象成员将导致该成员指向新值,而不是名称 ab。它们仍将指向同一个对象。

我怎样才能在(a, b) (c, d) 类示例中执行类似的操作?也就是说,复制对象然后更改它的一部分(这不会影响我借用属性的对象)?

Python 2.7.10:8.17. copy — Shallow and deep copy operations

Python 中的赋值语句不会复制对象,它们会在目标和对象之间创建绑定。对于可变或包含可变项的集合,有时需要一个副本,以便可以更改一个副本而不更改另一个副本。该模块提供通用的浅拷贝和深拷贝操作(解释如下)。

界面总结:

copy.copy(x)

返回x 的浅拷贝。

copy.deepcopy(x)

返回x 的深层副本。

>>> import copy
>>> c = number("one", 1)
>>> d = copy.copy(c)        # Make a copy of object c  
>>> id(d), id(c)
(42898856, 43279384)  

修改cd 对象中的任何一个都不会相互影响。


参考资料:

【讨论】:

    【解决方案2】:

    将对象引用分配给变量完全就像将数字分配给变量一样。它们都不涉及复制任何东西。在 Python 的智能祖先 Lisp 和 Smalltalk 中,情况完全相同。只是碰巧不能改变数字。

    >>> x = 10**50
    >>> y = 10**50
    >>> z = x
    >>> x is y
    False
    >>> x is z
    True
    

    我们在这里看到 Python 区分了复制(x 和 y 相等但不同)和不复制(x 是 z),并且分配一个数字复制。

    【讨论】:

      【解决方案3】:

      “名字里有什么?我们称之为玫瑰的东西

      任何其他名字都会闻起来很香;"

      --- 第二幕,第二场罗密欧朱丽叶

      在 Python 中,

      • 变量只是对对象或文字的符号引用
      • 对象是类型或类的实例
      • 除非明确强制,否则所有变量都通过引用复制(赋值、参数传递和函数返回值)

      现在让我们理解你的例子

      a 和 b 指的是两个不同的整数字面量

      a = 3
      b = a
      
      print(a, b)
      

      b 现在指的是一个新的整数文字 (4)

      b=4
      

      c 指的是一个对象,它是类number的实例

      c = number("one", 1)
      

      d 指代 c 指向的同一个对象

      d = c
      

      更改 d 引用的对象的 number 属性。由于 d 和 c 引用同一个对象,这也会影响 c。

      d.number = 2
      

      【讨论】:

        【解决方案4】:

        一张价值千言万语的图片

        a = 3
        b = a
        c = number("one", 1)
        d = c
        


        第 2 步……

        b = 4
        d.number = 2
        

        您可以看到为什么更改d.number 也会影响c


        如果在第 2 步之前,您这样做了

        import copy
        d = copy.copy(c)
        

        ...那么cd 是独立的。更改d.number 不会影响c

        【讨论】:

        • 所以......为了澄清......让我们假设number 类包含一个属性numbers,它引用了一个数字列表(而不是number)。在这种情况下,根据我对copy documentation 的阅读,您可能希望使用copy.deepcopy(),因为copy.copy() 会将c 中对numbers 列表的引用插入d,但是@ 987654343@ 将创建一个重复列表。我说的对吗?
        【解决方案5】:

        这些行:

        c = number("one", 1)
        d = c
        

        ...有效:

        • 创建number 的新实例并将其分配给c
        • 将名为c 的现有引用分配给新变量d

        您没有更改或修改有关c 的任何内容; d 是指向同一个实例的另一个名称。

        如果不克隆实例或创建新实例,您将无法执行与原始 int 行为类似的任何操作。


        为了纠正一些信息,上面的解释是rather simplified 和有点不完整的in its nature,尽管它主要描述了 10,000 英尺处发生的事情。

        为了仔细观察,我们必须了解一些关于 Python 变量或“名称”的事情,以及它们如何与这个程序交互。

        如上所述,you have the notion of "names" and "bindings",这很容易理解:

        a = 3
        b = a
        

        在此上下文中,a 是一个名称,b 是与 a 的绑定。我们没有修改或更改关于 a 的任何内容。

        如前所述,Python 中有两种类型的数据:可变的和不可变的。指向不可变数据(例如原语和元组)的名称可以重新分配,而不会对其上存在的任何其他绑定产生任何不良影响,因为相对于绑定没有任何状态发生变化。

        这就是为什么这次重新分配符合我们的预期:

        print(a, b)
        b = 4
        print(a, b)
        

        b = 4 的结果是b 现在指向一个整数的新副本,值 4。

        回想一下,我确实提到元组是不可变数据。您不能更改元组中特定实体的绑定...

        t = ('foo', 'bar')
        t[0] = 'baz' # illegal
        

        ...但是您可以将可变数据结构作为这些绑定的一部分。

        t = ([1, 2, 3], 'bar')
        t[0].append([4, 5, 6]) # ([1, 2, 3, [4, 5, 6]], 'bar')
        

        那么我们的例子在哪里呢?

        c = number("one", 1)
        d = c
        

        number是一个mutable类型,被命名为c,它的值可以在多个不同的绑定到c之间随意改变。

        实际上,我们有一个名字和一个名字的绑定:

        • 我们有一个number 的新实例,并通过名称c 引用它。
        • 将引用 c 绑定到另一个名称 d

        同样,c 没有任何变化,但可以通过其他名称引用它。

        与不可变数据不同,当我们重新分配 d.number 的值时,我们正在重新分配 c 知道的相同绑定:

        >>> id(d.number)
        36696408
        >>> id(c.number)
        36696408
        

        这就是您需要新实例或副本的原因。您必须引用number 的另一个实例。使用这种简单的绑定,您将无法做到这一点。

        from copy import copy
        c = number("one", 1)
        d = copy(c)
        id(c) # 140539175695784
        id(d) # 140539175695856
        

        【讨论】:

        • 这是正确的答案,但我想补充一点,Python 并没有真正的变量。它们更像是标签。在第一种情况下 (a, b), a => 3, b=>3 然后 b=>4 所以你正在修改 2 个不同的标签。对于情况2(c,d),c和d是指向同一个Object的两个不同的标签,所以Object.number只是一个标签。
        • 这是正确的,但它并没有真正解决问题。问题不在于这些行,因为第一个示例中的相应行做同样的事情。问题在于 other 行,这些行后来改变了 d,而不是像 b 那样将名称绑定到新对象。
        • 公平点。当我在电脑附近时,我会更正它并合并 Shay 的信息。
        • 我已经合并了相当数量的更改和修复以及一些外部参考。 10,000 英尺的视野很好,但了解内部运作也是一个加分项。
        【解决方案6】:

        这在其他答案中没有得到澄清,但如果我没记错的话,python 中的整数实际上并不是原语。它们是不可变的整数对象。因此,当您有多个变量都保存值 3 时,实际上您对保存值 3 的单个 Integer 实例有多个引用。

        所以考虑到这一点;

        a = 3
        b = a
        

        导致变量 a 和 b 都指向持有值 3 的整数。同样,

        c = number("one", 1)
        d = c
        

        导致变量 c 和 d 都指向同一个数字实例。

        同样的事情发生在

        b = 4
        

        现在 b 是对整数 4 的引用。

        但是,当 d.number 被设置时

        d.number = 2
        

        c 和 d 都指向的类“number”的实例的成员变量“number”被更新为引用 Integer 2。这里的重要概念是您正在修改一个成员变量由两个单独的变量引用的对象。

        来源: http://www.laurentluce.com/posts/python-integer-objects-implementation/

        【讨论】:

        • 这不是问题的答案。
        【解决方案7】:

        造成这种差异的原因是不可变对象与不可变对象 - int 是不可变对象,您的类是可变对象。

        在此示例中(类似于您的第一个示例),xy 都指向同一个不可变对象(整数,1)。该对象永远不会改变,因此当您分配 y = 2 时,y 现在指向一个不同不可变对象 (int(2))。

        >>> x = 1  # x points to the object int(1).
        >>> y = x  # So does y.
        >>> assert x is y
        >>> y = 2
        >>> assert x is not y
        >>>    
        

        你的类是一个可变对象,类似于一个列表。在第二个示例中(类似于您的第二个示例),xy 都指向同一个可变对象 ([])。当您将0 附加到它时,它会将其放置在它们都引用的列表中。当您将[0] 分配给y 时,您将一个不同 列表分配给y,但该列表恰好等于x,即使它是一个不同的对象。

        >>> x = []
        >>> y = x
        >>> assert x is y  # x and y reference the same object.
        >>> y.append(0)  # We append 0 to the list that both x and y reference.
        >>> x
        [0]
        >>> y
        [0]
        >>>
        >>> y = [0]  # Creating a new list object.
        >>> assert x is not y  # x and y are different objects...
        >>> assert x == y  # ...but they are equal.
        >>>    
        

        【讨论】:

        • 我对这个特征有点恼火。问题不在于不可变或可变的对象。问题在于变异和非变异操作。正如您的示例所示,即使 y 是一个可变列表,y = [0] 也不会对其进行变异。重要的不是y 可以 变异,而是y.append 实际上变异它(就像问题中的d.number = 2 变异d)。
        • 事实上,small integers aren't completely immutable in Python, if you're willing to muck around with the internals。整数的“不变性”与对象的可变性并不是使代码的行为方式如此的原因。这是我们没有尝试改变整数的事实。
        • 换句话说,如果您将number 类定义为不可变的(例如,使用collections.namedtuple 实现),那么cd 的行为将不会像ab。相反,在尝试设置 d.number = 2 时会出现错误。
        • @200_success - 好的,我点击了how to make 1 = 2 in Python 的链接(好的,不是真正的标题,但也可能是 :-D )。我只能说,“哦,我的头疼。”所以,听起来我已经很接近目标了。你建议我从哪里开始阅读以缩小我的知识差距?
        • @DougR.:有一些文章讨论了它是如何工作的。 Here 很短。 Here 更长。
        【解决方案8】:

        我没有看到有人提供了有关如何通过复制对象而不是仅仅为同一对象分配新引用来使这两种情况相同工作的详细信息。

        import copy
        c = number("one", 1)
        d = c
        e = copy.copy(c)
        
        print(c.number, d.number, e.number)
        d.number = 2
        e.number = 5
        print(c.number, d.number, e.number)
        

        这会给你:

        1 1 1
        2 2 5
        

        【讨论】:

        • copy.copy.copy.copy.copy()
        • @Inverse 我不明白你评论的意思
        • 这如何回答这个问题?
        【解决方案9】:

        您关注的是这两对行是相同的(都使用普通的=):

        # one
        a = 3
        b = a
        
        #two
        c = number("one", 1)
        d = c
        

        你缺少的是这两行是不同的:

        # one
        b = 4
        
        # two
        d.number = 2
        

        它们不一样的原因是d.number 有一个点,而b 没有。

        设置d = c确实与设置b = a具有相同的效果。不同之处在于执行d.number = 2 与执行b = 4相同。当您执行b = 4 时,您将一个新对象分配给名称b。当您执行d.number = 2 时,您修改了已由名称d 引用的对象,而不分配新对象。如果您将第二个示例更改为d = 2,使用普通赋值而不是属性赋值,您将看到c 不受影响,就像您的第一个示例中a 不受影响一样。

        虽然它可能会令人困惑,但= 在 Python 的所有上下文中并不总是意味着相同的东西。分配给一个裸名 (blah = ...) 与分配给一个属性 (blah.attr = ...) 或一个项目 (blah[item] = ...) 是不同的。要了解= 的含义,您需要查看左侧(赋值目标),看看它是一个简单的名称还是某种表达式。

        至于如何在你的c/d例子中得到你的a/b例子的效果,就看你想要什么效果了。如果你想结束 dc 指向具有不同 number 属性的不同对象,你可以这样做:

        d = number("one", 2)
        

        请注意,这现在与 b = 4 平行(因为它使用对裸名称的赋值)。还有其他更复杂的解决方案,包括复制现有的d 对象;查看标准库中的copy 模块。具体做什么取决于您要使用代码完成什么。

        【讨论】:

        • 赋值运算符总是一样的。它总是分配一个参考。你把它复杂化了很多很多
        • @kirbyfan64sos:不,不是。 a = b 总是将名称绑定到引用;对象a 无法控制此操作,实际上a 甚至可能以前都不存在。 a.attr = b调用a.__setattr__('attr', b),表示a已经存在,整个操作由a控制。 a.attr = b 可以复制 b,或者改变 b,或者分配一个完全不同的值,或者什么都不做,或者删除你的硬盘,如果这是 a 决定做的。 a = b 只能将 b 绑定到名称 a
        • 但是(假设用户没有覆盖 setattr),它最终还是会分配一个引用。
        • @kirbyfan64sos:但假设用户没有覆盖__setattr__ 正是问题所在。不同的是,使用a=...,你不需要假设任何东西,因为它不能自定义;使用a.x=...,如果不知道a 是什么以及它定义了什么行为,您就无法知道会发生什么。如果a 不想要,a.x=... 可能不会分配任何东西。此外,在这个问题的上下文中,d.number = 2 是对数字 2 “分配引用”还是分配副本或其他值都没有关系;问题在于对对象 d 的引用,而不是数字 2。
        【解决方案10】:

        在 Python 中,一切都是参考。以这个为例:

        a = '1221398210p3'
        b = a
        print(id(a) == id(b)) # True
        

        现在,当你这样做时:

        b = 4
        

        您只是在更改数字 b references。不是号码本身。现在,在你后面的例子中:

        c = number("one", 1)
        d = c
        ...
        d.number = 2
        

        d = c 将 d 设置为引用 c。现在,当您设置d.number 时,您正在为 c 和 d 引用的对象设置属性。就像我说的,一切都是参考。

        在 Python 中,变量更像是 标签(或者,在 C 术语中,指针):它们不是值。他们只是指向真正的价值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-12-18
          • 2010-11-06
          • 2012-05-12
          • 2011-05-13
          • 1970-01-01
          • 2011-01-23
          • 1970-01-01
          • 2011-06-08
          相关资源
          最近更新 更多