想象一种名为 FakeMutablePython 的语言,您可以使用列表赋值等方式(例如 mystr[0] = 'a'
)来更改字符串
a = "abc"
这将在内存地址0x1中创建一个指向"abc"的条目,并且标识符a
指向它。
现在,假设你执行..
b = a
这将创建标识符b
并指向内存地址为0x1的相同位置。
现在,如果该字符串是可变的,并且您更改了b
:
b[0] = 'z'
这会改变存储在0x1的字符串的第一个字节为z
。由于标识符a
指向此处,因此该字符串也将被改变,所以...
print a
print b
...两者都会输出zbc
这可能会导致一些非常奇怪、意外的行为。 字典键是一个很好的例子:
mykey = 'abc'
mydict = {
mykey: 123,
'zbc': 321
}
anotherstring = mykey
anotherstring[0] = 'z'
在 FakeMutablePython 中,事情变得相当奇怪 - 最初字典中有两个键,"abc" 和 "zbc"。然后你通过标识符 anotherstring
修改了 "abc" 字符串为 "zbc",所以这个字典现在有两个键,"zbc" 和 "zbc"...
解决这种怪异现象的一个方法是,每当你将一个字符串赋值给一个标识符(或将其用作字典键)时,将字符串从 0x1 复制到 0x2。
这可以防止上述问题,但如果你有一个需要 200MB 内存的字符串呢?
a = "really, really long string [...]"
b = a
你的脚本突然占用了400MB的内存?这并不好。
如果我们将它指向同一内存地址,直到我们修改它呢?写时复制。问题是,这可能会相当复杂。
这就是不可变性体现的地方。不需要.replace()
方法将字符串从内存复制到新的地址,再进行修改和返回,我们只需使所有字符串不可变,因此该函数必须创建一个新的字符串来返回。这解释了下面的代码:
a = "abc"
b = a.replace("a", "z")
并且已被证明:
>>> a = 'abc'
>>> b = a
>>> id(a) == id(b)
True
>>> b = b.replace("a", "z")
>>> id(a) == id(b)
False
(id()
函数返回对象的内存地址)
"abc"
和 "abcd"
是不同的、完全无关的对象 - https://dev59.com/am025IYBdhLWcg3w6aYX - dbr将它们定义为不可变的一个显著优势是可以将它们用作字典中的键。如果允许更改键,则字典使用的内部数据结构可能会混乱。
'abc'.__hash__()
来验证这一点。 - Kenan Banksd.keys()
来证明这一点。你可以很容易地拥有两个具有相同哈希值的字符串,而字典将保持它们分开。 - Mark Ransom不可变类型比可变类型在概念上要简单得多。例如,在C++中,您不必处理复制构造函数或const-correctness等问题。类型越不可变,语言就越容易。因此,最简单的语言是没有任何全局状态的纯函数式语言(因为λ演算比图灵机更容易,而且同样强大),尽管很多人似乎不太欣赏这一点。
优点:性能好。
缺点:无法更改可变对象。
bytearray
和list
拼写。 - SingleNegationElimination