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

56
我不确定为什么字符串和元组被设计成不可变的;将它们设计成不可变的优缺点是什么?

1
除了Python解释器的内部实现之外,这种设计在编写程序时是否有意义?(例如,如果元组和字符串是可变的,会使编程更容易吗?)如果是这样,选择不可变元组与列表的例子是什么?(或者,可变字符串与Python字符串相比如何?) - user186477
3
有一种编程风格叫做函数式编程,其中所有内容都是不可变的。参见维基百科:http://zh.wikipedia.org/wiki/函数式编程 - Mark Ransom
2
Python确实有可变字符串和元组;分别用bytearraylist拼写。 - SingleNegationElimination
6个回答

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()函数返回对象的内存地址)


2
我听过的最好的解释! - Tommy Crush
如果我说a="abc",b="abcd",它会共享abc吗?比如说b[:4]是a吗? - Dineshkumar
@Dineshkumar 不,我非常确定 "abc""abcd" 是不同的、完全无关的对象 - https://dev59.com/am025IYBdhLWcg3w6aYX - dbr
@dbr 你说“写时复制……可能会非常复杂……”但是实现不可变性难道不同样复杂吗?你仍然需要知道是否进行了修改,如果是,就需要创建另一个带有所需修改的“实例”。 - flow2k
@flow2k,没有不可变性更容易。Python知道哪些操作可以修改字符串,并强制它们创建一个新的字符串对象——即使该字符串与您开始的字符串相同。 - Mark Ransom

35
一方面,性能是一个关键因素:知道字符串是不可变的使得在构造时很容易进行布局——固定和不变的存储需求。这也是元组和列表之间区别的原因之一。这也允许实现安全地重用字符串对象。例如,CPython 实现使用预分配的对象来表示单个字符的字符串,并且通常对于不更改内容的字符串操作返回原始字符串。
另一方面,在 Python 中,字符串被视为与数字一样“基本”。任何活动都不会将值 8 更改为其他值,同样,在 Python 中,任何活动都不会将字符串“eight”更改为其他值。

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


1
这并没有解释为什么元组是不可变的。 - aCuria
1
链接到effbot已经失效了,也许可以用互联网档案馆的链接替换一下?https://web.archive.org/web/20201031092707/http://effbot.org/pyfaq/why-are-python-strings-immutable.htm - antonagestam

10

将它们定义为不可变的一个显著优势是可以将它们用作字典中的键。如果允许更改键,则字典使用的内部数据结构可能会混乱。


4
但是你可以使用任何用户创建的对象实例作为键,它们显然是可变的。然后,“键”可能只是内存地址,如果字符串是可变的,你仍然可以通过它们独特的内存地址来进行键控。 - Kenan Banks
@Triptych 这对于字符串来说可能不是你想要的,你需要按值进行键控,否则字典将毫无用处... - Hejazzman
@Hejazzman,这不是Python字典的工作方式。文本字符串值不会被用作字典键,而是会取其哈希值。你可以通过 'abc'.__hash__() 来验证这一点。 - Kenan Banks
1
@Triptych,你说的一切都是错误的。首先,你可以有两个相等的字符串,但它们的地址不同,所以使用地址是行不通的。其次,虽然字典使用字符串的哈希值,但实际上是字符串本身作为键 - 通过显示 d.keys() 来证明这一点。你可以很容易地拥有两个具有相同哈希值的字符串,而字典将保持它们分开。 - Mark Ransom
@Mark Ransom 不是地址,而是哈希值。根据定义,您不能有两个字符串进行比较,它们相等但具有不同的哈希值。 - Kenan Banks
显示剩余2条评论

4

不可变类型比可变类型在概念上要简单得多。例如,在C++中,您不必处理复制构造函数或const-correctness等问题。类型越不可变,语言就越容易。因此,最简单的语言是没有任何全局状态的纯函数式语言(因为λ演算比图灵机更容易,而且同样强大),尽管很多人似乎不太欣赏这一点。


3

优点:性能好。

缺点:无法更改可变对象。


11
优点:你无法改变它们。 - Matt Ellen

3
Perl拥有可变字符串并且似乎运行良好。上述内容看起来像是对任意设计决策的推波助澜和辩解。
为什么Python拥有不可变字符串?因为Python的创建者Guido van Rossum想要这样,现在他拥有无数支持者会捍卫这个任意决策直到他们生命的最后一刻。
你也可以提出一个类似的问题,为什么Perl没有不可变字符串,然后一大堆人会写下为什么不可变字符串的概念非常糟糕以及为什么Perl没有它是最好的想法。

4
Perl实际上没有字符串:它有标量(scalar),可以作为字符串或数字(后者有多种类型)来运作。如果标量是不可变的,那么它将成为纯函数式Perl,全世界的Perl开发人员都会通过将undef赋值给自己来自杀。 - Walter A. Aprile

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接