为什么Python字符串和元组是不可变的?

56 投票
6 回答
18847 浏览
提问于 2025-04-15 14:55

我不太明白为什么字符串和元组会被设计成不可改变的;把它们设定为不可改变有什么好处和坏处呢?

6 个回答

10

把它们设为不可变的一个大好处是,它们可以用作字典中的键。要是字典里的键可以随意改变,字典内部的数据结构肯定会变得很混乱。

86

想象一下有一种语言叫做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() 函数返回对象的内存地址)

35

首先,谈谈性能:知道字符串是不可变的,这让我们在创建字符串的时候就能很清楚地知道它的存储需求是固定的,不会改变。这也是元组和列表之间区别的一个原因。此外,这种特性还允许程序更安全地重复使用字符串对象。比如,CPython的实现会为单个字符的字符串预先分配对象,并且通常在进行不改变内容的字符串操作时,会返回原来的字符串。

其次,在Python中,字符串被视为和数字一样“基本”。就像数字8无论怎么操作都不会变成其他值一样,字符串“eight”也不会因为任何操作而改变。

https://web.archive.org/web/20201031092707/http://effbot.org/pyfaq/why-are-python-strings-immutable.htm

撰写回答