可变与不可变
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
的副本。c
是 b
。
行使
现在你更好地理解一个可变类型隐含的副作用,你能解释一下这个例子中出了什么问题吗?
>>> 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...