来自`raw_input()`的字符串在内存中

时间:2013-02-16 01:33:08

标签: python python-2.7

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

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

3 个答案:

答案 0 :(得分: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需要检查以确保两个字符串都是等效的。 可以进行字符串比较,如果它们没有被实现,但是如果它们被实现,它只需要进行指针比较,这样会更有效率。

答案 1 :(得分:2)

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

答案 2 :(得分:2)

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

让我们从如何开始:Python使用" interned"字符串 - 来自wikipedia

  

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

为什么呢?好像拯救记忆不是这里的主要目标,只是一个很好的副作用。

  

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

     

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

现在"当":cpython"实习生"以下情况中的字符串:

  • 当您使用intern()非必要的内置函数(在Python 3中移动到sys.intern时)。
  • 小字符串(0或1字节) - this very informative article by Laurent Luce解释实现
  • Python程序中使用的名称会自动实现
  • 用于保存模块,类或实例属性的词典具有实习键

对于其他情况,每个实现似乎都会在字符串自动实现的时候有很大的变化。

我不可能比Alex Martinelli中的this answer做得更好(难怪这家伙有24.5万的名声):

  

Python语言的每个实现都可以自由地在分配不可变对象(例如字符串)时做出权衡 - 要么创建一个新的,要么找到一个相同的并且再使用一个对它的引用,就好了从语言的角度来看。当然,在实践中,现实世界的实现需要合理的妥协:在定位这样的对象时,再一次提及合适的现有对象既便宜又容易,只要找到一个合适的现有对象(可能是可能不存在)看起来可能需要很长时间才能进行搜索。

     

因此,例如,在单个函数中多次出现相同的字符串文字(在我知道的所有实现中)都使用"对同一对象的新引用"策略,因为在构建该函数的常量时,它可以非常快速且容易地避免重复;但是跨越不同的功能这样做可能是一项非常耗时的任务,因此现实世界的实施要么根本不做,要么只在一些启发式确定的案例子集中进行,人们可以希望合理编译时间的权衡(通过搜索相同的现有常量减慢)与内存消耗的比较(如果继续保留新的常量副本,则增加)。

     

我不知道Python的任何实现(或者其他具有常量字符串的语言,例如Java),在阅读时需要识别可能的重复项(通过多个引用重用单个对象)来自文件的数据 - 它似乎并不是一个很有前景的权衡(在这里你需要支付运行时间,而不是编译时间,因此权衡更具吸引力)。当然,如果您知道(由于应用程序级别的考虑因素)这些不可变对象很大并且很容易出现重复,那么您可以实现自己的" constants-pool"策略非常容易(实习生可以帮助你为字符串做这件事,但是你不难自己动手,例如,带有不可变项目的元组,巨大的长整数等等。)


[初步答复]

这是一个评论而非答案,但评论系统不适合发布代码:

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

在他的answer to another question中,用户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运算符)。