考虑下面的代码。它的输出是
1 385712698864 385712698864
2 385744287024
3 385744287088
4 385712698864
5 385744286960
6 385744286960
7 385744286960
8 385712698864
这意味着下面代码中的一些操作会改变id,但有些操作不会,即使没有操作改变变量a
的值:
- 将变量值设置为
"a"
总是会产生相同的id(在这个特定的运行中,是385712698864
) - 使用
a.lower()
在每次呼叫后改变a
的id a[::-1]
更改ida[:1]
不改变idg(a)
不改变idf(a)
更改id
有人能解释一下这种看似不一致的行为吗?(我使用python 3.8)
代码:
def f(x):
y = x + x
n = len(x)
return y[:n]
def g(x):
return "" + x
a = "a"
b = "a"
print(1, id(a), id(b))
a = a.lower()
print(2, id(a))
a = a.lower()
print(3, id(a))
a = "a"
print(4, id(a))
a = a[::-1]
print(5, id(a))
a = a[:1]
print(6, id(a))
a = g(a)
print(7, id(a))
a = f(a)
print(8, id(a))
Python字符串是不可变的,因此(一般情况下)任何对字符串执行的操作都会返回一个新字符串。作为CPython(标准Python实现)的实现细节,id(x)
通常返回x
的内存地址。有时,Python解释器很容易识别在哪里可以重用现有字符串并节省一些内存(这被称为"实习",并在Python中其他不可变类型的上下文中讨论),在这些情况下,'两个'字符串将具有相同的id
。
以一个相等的字符串赋值给两个不同的变量为例。解释器足够聪明,可以缓存文字字符串值(即令牌"a"
),并在内存中使用相同的字符串来表示这些值。这很好,因为无论如何都不能改变字符串,也不会有发生意外的危险。
您可以在示例1和示例4中看到这种实习:因为解释器已经缓存了"a"
,所以它们被赋予相同的ID:
a = "a" * 20
b = "a" * 20
assert id(a) == id(b) # True
对于较长的字符串,这种行为不会发生:
a = "a" * 10_000
b = "a" * 10_000
assert id(a) == id(b) # raises AssertionError
如果您使用变量来更改字符串的长度,也不会发生这种情况,因为解析器不太明显,这将导致相同的字符串:
>>> n = 20
>>> a = "a" * n
>>> b = "a" * n
>>> assert id(a) == id(b) # raises AssertionError
在另外两种情况下(6和7),您不会对字符串的长度或排列造成任何改变:
string[:len(string)]
优化到string
- 添加空字符串不会改变现有字符串
解释器能够将这些优化为无操作。
在示例5和8中,解释器不可能在不实际执行操作的情况下知道字符串是否会被更改(例如,我们知道a[::-1] == a
,但检查它所需要的工作量与创建一个新字符串一样多!),因此它将返回一个新字符串。