我不确定为什么字符串和元组被设计成不可变的;使它们不可变有哪些优点和缺点?
想象一种名为 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”。然后你将“abc”字符串(通过标识符
anotherstring
)更改为“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()
函数返回对象的内存地址)
一个是性能:知道 字符串是不可变的,因此很容易 在施工时就布置好—— 固定不变的存储 要求。这也是其中之一 之间的区别的原因 元组和列表。这也使得 安全地重用字符串的实现 对象。例如,CPython 实现使用预先分配的 单字符字符串的对象, 并且通常会返回原始的 string 用于字符串操作 不改变内容。
另外一个就是Python中的字符串 被视为“基本” 数字。任何活动都不会 将值 8 更改为其他值, 在Python中,没有大量的活动 会将字符串“八”更改为 还有什么。
使它们不可变的一大优点是它们可以用作字典中的键。我确信如果允许更改键,字典使用的内部数据结构会变得非常混乱。
不可变类型在概念上比可变类型简单得多。例如,您不必像 C++ 中那样搞乱复制构造函数或常量正确性。不可变的类型越多,语言就越容易。因此,最简单的语言是没有任何全局状态的纯函数式语言(因为 lambda 演算比图灵机容易得多,而且同样强大),尽管很多人似乎并不欣赏这一点。
Perl 具有可变字符串,并且似乎运行良好。 以上似乎是对任意设计决策的大量挥手和合理化。
我对为什么 Python 具有不可变字符串的问题的回答是,因为 Python 的创建者 Guido van Rossum 希望如此,而他现在拥有大批粉丝,他们将捍卫这一任意决定直至临终。
你可以提出一个类似的问题,为什么 Perl 没有不可变字符串,而一大群人会写不可变字符串的概念是多么糟糕,以及为什么它是 Perl 没有的最好的想法(TM)他们。
优点:性能
缺点:你不能改变可变的。