3

我早就知道 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

我很好奇为什么会这样?有技术原因吗?

4

3 回答 3

3

在我看来,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 需要检查以确保两个字符串是等价的。如果它们没有被实习,它可以做一个字符串比较,但如果他们被实习,它只需要做一个更有效的指针比较。

于 2013-02-16T01:35:43.973 回答
2

编译器不能intern字符串,除非它们出现在实际源代码中(即字符串文字)。除此之外,raw_input还剥离了新的线路。

于 2013-02-16T01:35:52.960 回答
2

[更新] 为了回答这个问题,有必要知道 Python 重用字符串的原因、方式和时间。

让我们从如何开始:Python 使用来自wikipedia的“interned”字符串:

在计算机科学中,字符串实习是一种仅存储每个不同字符串值的副本的方法,该副本必须是不可变的。驻留字符串使某些字符串处理任务更节省时间或空间,但代价是创建或驻留字符串时需要更多时间。不同的值存储在字符串实习池中。

为什么?似乎节省内存不是这里的主要目标,只是一个很好的副作用。

字符串实习加速了字符串比较,这有时是严重依赖带有字符串键的哈希表的应用程序(例如编译器和动态编程语言运行时)的性能瓶颈。在没有实习的情况下,检查两个不同的字符串是否相等涉及检查两个字符串的每个字符。这很慢有几个原因:字符串的长度本质上是 O(n);它通常需要从多个内存区域读取,这需要时间;并且读取会填满处理器缓存,这意味着可用于其他需求的缓存更少。使用实习字符串,在原始实习操作之后进行简单的对象身份测试就足够了;这通常被实现为指针相等测试,通常只是一条没有内存引用的机器指令。

如果有许多相同字符串值的实例,字符串实习也可以减少内存使用;例如,它是从网络或存储中读取的。这样的字符串可能包括幻数或网络协议信息。例如,XML 解析器可能会保留标记和属性的名称以节省内存。

现在 "when": cpython "interns" 一个字符串在以下情况下:

  • 当您使用intern()非必要的内置函数(sys.intern在 Python 3 中移至)时。
  • 小字符串(0 或 1 字节)- Laurent Luce 的这篇内容丰富的文章解释了实现
  • Python 程序中使用的名称是自动实习的
  • 用于保存模块、类或实例属性的字典具有内部键

在其他情况下,每个实现似乎在字符串何时自动实习方面都有很大的变化。

我不可能比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运算符)。

于 2013-02-16T01:41:56.753 回答