我早就知道 Python 喜欢在内存中重用字符串而不是重复:
>>> a = "test"
>>> id(a)
36910184L
>>> b = "test"
>>> id(b)
36910184L
但是,我最近发现从返回的字符串raw_input()
不遵循典型的优化模式:
>>> a = "test"
>>> id(a)
36910184L
>>> c = raw_input()
test
>>> id(c)
45582816L
我很好奇为什么会这样?有技术原因吗?
我早就知道 Python 喜欢在内存中重用字符串而不是重复:
>>> a = "test"
>>> id(a)
36910184L
>>> b = "test"
>>> id(b)
36910184L
但是,我最近发现从返回的字符串raw_input()
不遵循典型的优化模式:
>>> a = "test"
>>> id(a)
36910184L
>>> c = raw_input()
test
>>> id(c)
45582816L
我很好奇为什么会这样?有技术原因吗?
在我看来,python实习生字符串文字,但通过其他进程创建的字符串不会被实习:
>>> s = 'ab'
>>> id(s)
952080
>>> g = 'a' if True else 'c'
>>> g += 'b'
>>> g
'ab'
>>> id(g)
951336
当然,raw_input
在不使用字符串文字的情况下创建新字符串,因此假设它不会具有相同的id
. C-python实习生字符串有(至少)两个原因 - 内存(如果你不存储同一事物的一大堆副本,你可以保存一堆)和哈希冲突的解决方案。如果 2 个字符串散列到相同的值(例如在字典查找中),则 python 需要检查以确保两个字符串是等价的。如果它们没有被实习,它可以做一个字符串比较,但如果他们被实习,它只需要做一个更有效的指针比较。
编译器不能intern
字符串,除非它们出现在实际源代码中(即字符串文字)。除此之外,raw_input
还剥离了新的线路。
[更新] 为了回答这个问题,有必要知道 Python 重用字符串的原因、方式和时间。
让我们从如何开始:Python 使用来自wikipedia的“interned”字符串:
在计算机科学中,字符串实习是一种仅存储每个不同字符串值的副本的方法,该副本必须是不可变的。驻留字符串使某些字符串处理任务更节省时间或空间,但代价是创建或驻留字符串时需要更多时间。不同的值存储在字符串实习池中。
为什么?似乎节省内存不是这里的主要目标,只是一个很好的副作用。
字符串实习加速了字符串比较,这有时是严重依赖带有字符串键的哈希表的应用程序(例如编译器和动态编程语言运行时)的性能瓶颈。在没有实习的情况下,检查两个不同的字符串是否相等涉及检查两个字符串的每个字符。这很慢有几个原因:字符串的长度本质上是 O(n);它通常需要从多个内存区域读取,这需要时间;并且读取会填满处理器缓存,这意味着可用于其他需求的缓存更少。使用实习字符串,在原始实习操作之后进行简单的对象身份测试就足够了;这通常被实现为指针相等测试,通常只是一条没有内存引用的机器指令。
如果有许多相同字符串值的实例,字符串实习也可以减少内存使用;例如,它是从网络或存储中读取的。这样的字符串可能包括幻数或网络协议信息。例如,XML 解析器可能会保留标记和属性的名称以节省内存。
现在 "when": cpython "interns" 一个字符串在以下情况下:
intern()
非必要的内置函数(sys.intern
在 Python 3 中移至)时。在其他情况下,每个实现似乎在字符串何时自动实习方面都有很大的变化。
我不可能比Alex Martinelli在这个答案中做得更好(难怪这个人有 245k 的声誉):
Python 语言的每个实现都可以自由地在分配不可变对象(例如字符串)方面做出自己的权衡——无论是创建一个新对象,还是找到一个现有的相等对象并使用一个对它的更多引用,都可以从语言的观点看法。当然,在实践中,现实世界的实现会做出合理的折衷:在定位合适的现有对象时再引用一个合适的现有对象既便宜又容易,如果定位合适的现有对象的任务(可能或可能不存在)看起来可能需要很长时间搜索。
因此,例如,在单个函数中多次出现相同的字符串文字将(在我知道的所有实现中)使用“对同一对象的新引用”策略,因为在构建该函数的常量池时,它非常快速且容易避免重复;但是在不同的函数之间这样做可能是一项非常耗时的任务,因此现实世界的实现要么根本不这样做,要么只在一些启发式确定的情况下这样做,在这些情况下,人们希望合理权衡编译时间(通过搜索相同的现有常量而减慢)与内存消耗(如果不断制作新的常量副本会增加)。
我不知道 Python 的任何实现(或者就此而言,其他具有常量字符串的语言,例如 Java)在从文件中读取数据时会费力地识别可能的重复项(通过多个引用重用单个对象) - - 这似乎不是一个有希望的权衡(在这里你要支付运行时间,而不是编译时间,所以这种权衡更没有吸引力)。当然,如果您知道(由于应用程序级别的考虑)这样的不可变对象很大并且很容易出现许多重复,您可以很容易地实现自己的“常量池”策略(实习生可以帮助您为字符串做到这一点,但是例如,具有不可变项的元组、巨大的长整数等)滚动你自己的并不难)。
[初步回答]
这更像是一个评论而不是一个答案,但评论系统不太适合发布代码:
def main():
while True:
s = raw_input('feed me:')
print '"{}".id = {}'.format(s, id(s))
if __name__ == '__main__':
main()
运行它给了我:
"test".id = 41898688
"test".id = 41900848
"test".id = 41898928
"test".id = 41898688
"test".id = 41900848
"test".id = 41898928
"test".id = 41898688
根据我的经验,至少在 2.7 上,甚至对于raw_input()
.
如果实现使用哈希表,我想有不止一个。现在要深入研究源代码。
[第一次更新]
看起来我的实验有缺陷:
def main():
storage = []
while True:
s = raw_input('feed me:')
print '"{}".id = {}'.format(s, id(s))
storage.append(s)
if __name__ == '__main__':
main()
结果:
"test".id = 43274944
"test".id = 43277104
"test".id = 43487408
"test".id = 43487504
"test".id = 43487552
"test".id = 43487600
"test".id = 43487648
"test".id = 43487744
"test".id = 43487864
"test".id = 43487936
"test".id = 43487984
"test".id = 43488032
在他对另一个问题的回答中,用户tzot警告对象生命周期:
附注:了解 Python 中对象的生命周期非常重要。请注意以下会话:
Python 2.6.4 (r264:75706, Dec 26 2009, 01:03:10)
[GCC 4.3.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a="a"
>>> b="b"
>>> print id(a+b), id(b+a)
134898720 134898720
>>> print (a+b) is (b+a)
False
您认为通过打印两个单独表达式的 ID 并指出“它们是相等的,因此两个表达式必须相等/等价/相同”是错误的。单行输出并不一定意味着其所有内容都是在同一时刻创建和/或共存的。
如果您想知道两个对象是否是同一个对象,请直接询问 Python(使用
is
运算符)。