【问题标题】:The immutable object in pythonpython中的不可变对象
【发布时间】:2012-04-14 04:44:17
【问题描述】:

我看到一篇关于不可变对象的文章。

上面写着:
variable = immutable
将不可变分配给变量。

例如
a = b # b is a immutable
它说在这种情况下a 指的是a copy of b,而不是reference to b。 如果 b 是 mutable,则 a 将是 a reference to b

所以:

a = 10
b = a
a =20
print (b) #b still is 10

但在这种情况下:

a = 10
b = 10
a is b # return True
print id(10)
print id(a)
print id(b) # id(a) == id(b) == id(10)

如果a10的副本,b也是10的副本,为什么id(a) == id(b) == id(10)?

【问题讨论】:

  • 这不仅仅是可变性,而是将对象内的位置指向新对象(通过在 = 的左侧使用 some_name[some_item])和指向 之间的区别在新对象上命名(通过在= 的左侧仅使用some_name)。所以如果你有inner = [0]outer = [inner, inner],如果你有outer[0] = 1,你不会改变内部,outer变成[1, [0]]。因此,即使inner 是可变的,如果您不通过执行outer[0][0] 访问其中的位置,您也不会更改它,例如,第二个[0] 指向inner 内部。

标签: python variable-assignment immutability mutable


【解决方案1】:

“简单”不可变字面量(特别是 -1 到 255 之间的整数)是interned,这意味着即使绑定到不同的名称,它们仍然是同一个对象。 p>

>>> a = 'foo'
>>> b = 'foo'
>>> a is b
True

【讨论】:

  • 这并不能解释他帖子的前三分之二,只是后三分之一。
  • @agf:最后三分之一是有问题的部分。
  • 仍然需要指出的是,他从他阅读的文章中得出的结论是错误的。
  • 我不同意措辞含糊。
【解决方案2】:

虽然该文章对于某些语言可能是正确的,但对于 Python 来说却是错误的。

当你在 Python 中进行任何正常的赋值

some_name = some_name_or_object

你没有复制任何东西。您只是将名称指向作业右侧的对象。

可变性无关紧要。

更具体地说,原因:

a = 10
b = 10
a is b

True,是10 是被实习的——这意味着Python 在内存中保留一个10,任何设置为10 的东西都指向同一个10

如果你这样做

a = object()
b = object()
a is b

你会得到False,但是

a = object()
b = a
a is b

仍将是True

【讨论】:

  • Python 尝试实习 int 值,但并不总是成功。尝试a = 1000b = 999,然后是b += 1。当我刚刚尝试时,ab 分别绑定到不同的 int 对象,值为 1000。不过,我认为值 0 和 1 的实习总是会成功。
  • 这种情况:lst = [0, 1, 2] * 2,列表[0,1,2]是可变的,我改lst[0][0] = 5,lst[1][0]也会变成5,为什么?
  • @steveha 正如 Ignacio 在他的回答中提到的那样,这不是成功或失败,而是 -1 到 255。
  • @TankyWoo 我猜你的意思是[[0, 1, 2]] * 2。因为您获得了一个新列表,其中 两次 具有相同的项目,所以在任一位置更改它会使更改同时显示在两个位置。 Python 没有复制内部列表,只是将索引0 和索引1 都指向它。
  • @TankyWoo,列表的重复为您提供了对同一列表的两个引用。试试id(lst[0])id(lst[1])
【解决方案3】:

因为实习已经解释过了,我只讲可变/不可变的东西:

将不可变对象分配给变量。

在谈论实际发生的事情时,我不会选择这种措辞。

我们有对象(存在于内存中的东西)和访问这些对象的方法:名称(或变量),它们被“绑定”到引用的对象。 (你可以说点对象)

名称/变量是相互独立的,它们可以碰巧绑定到同一个对象,也可以绑定到不同的对象。重新定位一个这样的变量不会影响任何其他变量。

没有值传递或引用传递这回事。在 Python 中,您总是“按对象”传递/分配。在将变量分配或传递给函数时,Python 从不创建副本,它总是传递/分配您已经拥有的相同对象。

现在,当您尝试修改不可变对象时,会发生什么?如前所述,对象是不可变的,因此发生的情况如下:Python 创建一个修改后的 copy

至于你的例子:

a = 10
b = a
a =20
print (b) #b still is 10

这与可变性无关。在第一行,将值 10 的 int 对象绑定到名称 a。在第二行,将a 引用的对象绑定到名称b

在第三行,您将值 20 的 int 对象绑定到名称 a,这不会更改名称 b 所绑定的内容!

在这种情况下,a 指的是 b 的副本,而不是 b 的引用。如果 b 是可变的,a 将是对 b 的引用

如前所述,Python 中没有 references 这样的东西。 Python 中的名称绑定到对象。不同的名称(或变量)可以绑定到同一个对象,但不同的名称本身之间没有联系。当您修改事物时,您修改了对象,这就是为什么绑定到该对象的所有其他名称“看到更改”,它们绑定到您修改的同一个对象,对吧?

如果您将名称绑定到不同的对象,那么就会发生这种情况。其他名称没有任何魔法,它们保持原样。

关于列表的例子:

In [1]: smalllist = [0, 1, 2] 
In [2]: biglist = [smalllist]    
In [3]: biglist
Out[3]: [[0, 1, 2]] 

我可能写的是 In[1] 和 In[2],而不是:

In [1]: biglist = [[0, 1, 2]]
In [2]: smalllist = biglist[0]

这是等价的。

在这里看到的重要一点是 biglist 是一个包含一个项目的列表。这一项当然是一个对象。它是一个列表这一事实并没有让人联想到什么魔法,它只是一个恰好是一个列表的简单对象,我们将其附加到名称 smalllist

因此,访问 biglist[i] 与访问 smalllist 完全相同,因为它们是同一个对象。我们从不复制,我们传递了对象。

In [14]: smalllist is biglist[0]
Out[14]: True

因为列表是可变的,所以我们可以改变 smallist,并在 biglist 中看到变化。为什么?因为我们实际上修改了smallist所指的对象。我们仍然拥有相同的对象(除了它已更改的事实)。但是 biglist 会“看到”这种变化,因为作为它的第一项,它引用了同一个对象。

In [4]: smalllist[0] = 3
In [5]: biglist
Out[5]: [[3, 1, 2]]

当我们将列表“加倍”时也是如此:

In [11]: biglist *= 2
In [12]: biglist
Out[12]: [[0, 1, 2], [0, 1, 2]]

发生的事情是这样的:我们有一个列表:[object1, object2, object3](这是一个通用示例) 我们得到的是: [object1, object2, object3, object1, object2, object3]:它只会插入(即修改“biglist”)列表末尾的所有项目。同样,我们插入对象,我们不会神奇地创建副本。

所以当我们现在更改 biglist 的第一项内的一项时:

In [20]: biglist[0][0]=3
In [21]: biglist
Out[21]: [[3, 1, 2], [3, 1, 2]]

我们也可以只更改smalllist,因为出于所有意图和目的,biglist 可以表示为:[smalllist, smalllist]——它包含两次相同的对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多