可变与不可变

Python 中有两种类型。不可变类型和可变类型。

Immutables

无法更改不可变类型的对象。任何修改对象的尝试都将导致创建副本。

此类别包括:整数,浮点数,复数,字符串,字节,元组,范围和 frozensets。

为了突出这个属性,让我们玩 id 内置。此函数返回作为参数传递的对象的唯一标识符。如果 id 相同,则这是相同的对象。如果它改变了,那么这是另一个对象。 (有人说这实际上是对象的内存地址,但要小心它们,它们来自力量的黑暗面……)

>>> a = 1
>>> id(a)
140128142243264
>>> a += 2
>>> a
3
>>> id(a)
140128142243328

好吧,1 不是 3 ……突发新闻……也许不是。但是,当涉及到更复杂的类型(尤其是字符串)时,通常会忘记这种行为。

>>> stack = "Overflow"
>>> stack
'Overflow'
>>> id(stack)
140128123955504
>>> stack += " rocks!"
>>> stack
'Overflow rocks!'

啊哈! 看到?我们可以修改它!

>>> id(stack)
140128123911472

不。虽然看起来我们可以更改变量 stack 命名的字符串,但我们实际上做的是创建一个新对象来包含连接的结果。我们被愚弄了,因为在这个过程中,旧的物体无处可去,所以它被摧毁了。在另一种情况下,这将更加明显:

>>> stack = "Stack"
>>> stackoverflow = stack + "Overflow"
>>> id(stack)
140128069348184
>>> id(stackoverflow)
140128123911480

在这种情况下,很明显,如果我们想要保留第一个字符串,我们需要一个副本。但是对于其他类型来说这是如此明显吗?

行使

现在,知道不可变类型如何工作,你会用下面的代码说什么?这是明智的吗?

s = ""
for i in range(1, 1000):
    s += str(i)
    s += ","

Mutables

可以改变可变类型的对象,并且它可以原位改变。没有隐式副本。

此类别包括:列表,词典,字节数组和集合。

让我们继续玩我们的小 id 功能。

>>> b = bytearray(b'Stack')
>>> b
bytearray(b'Stack')
>>> b = bytearray(b'Stack')
>>> id(b)
140128030688288
>>> b += b'Overflow'
>>> b
bytearray(b'StackOverflow')
>>> id(b)
140128030688288

(作为旁注,我使用包含 ascii 数据的字节来明确我的观点,但请记住,字节不是为了保存文本数据而设计的。请原谅我。)

我们有什么?我们创建一个 bytearray,修改它并使用 id,我们可以确保这是同一个对象,经过修改。不是它的副本。

当然,如果要经常修改对象,则可变类型比不可变类型做得好得多。不幸的是,这种属性的现实经常被人们所遗忘。

>>> c = b
>>> c += b' rocks!'
>>> c
bytearray(b'StackOverflow rocks!')

好的…

>>> b
bytearray(b'StackOverflow rocks!')

Waiiit 第二……

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

确实。c 不是 b 的副本。cb

行使

现在你更好地理解一个可变类型隐含的副作用,你能解释一下这个例子中出了什么问题吗?

>>> ll = [ [] ]*4 # Create a list of 4 lists to contain our results
>>> ll
[[], [], [], []]
>>> ll[0].append(23) # Add result 23 to first list
>>> ll
[[23], [23], [23], [23]]
>>> # Oops...