Python何时为相同的string分配新的内存?

两个具有相同字符的Pythonstringa == b可以共享内存id(a)== id(b),或者可以在内存中两次,id(a)!= id(b)。 尝试

ab = "ab" print id( ab ), id( "a"+"b" ) 

这里Python认识到新创build的“a”+“b”与已经在内存中的“ab”相同 – 不错。

现在考虑一个N长的州名(“亚利桑那州”,“阿拉斯加州”,“阿拉斯加州”,“加利福尼亚州”)(在我的情况下是N〜500000)。
我看到50个不同的id()s⇒每个string“Arizona”…只存储一次,很好。
但是将列表写入磁盘并再次读回:“相同”列performance在具有N个不同的id(),方式更多的内存,见下文。

怎么会 – 任何人都可以解释Python的string内存分配?

 """ when does Python allocate new memory for identical strings ? ab = "ab" print id( ab ), id( "a"+"b" ) # same ! list of N names from 50 states: 50 ids, mem ~ 4N + 50S, each string once but list > file > mem again: N ids, mem ~ N * (4 + S) """ from __future__ import division from collections import defaultdict from copy import copy import cPickle import random import sys states = dict( AL = "Alabama", AK = "Alaska", AZ = "Arizona", AR = "Arkansas", CA = "California", CO = "Colorado", CT = "Connecticut", DE = "Delaware", FL = "Florida", GA = "Georgia", ) def nid(alist): """ nr distinct ids """ return "%d ids %d pickle len" % ( len( set( map( id, alist ))), len( cPickle.dumps( alist, 0 ))) # rough est ? # cf http://stackoverflow.com/questions/2117255/python-deep-getsizeof-list-with-contents N = 10000 exec( "\n".join( sys.argv[1:] )) # var=val ... random.seed(1) # big list of random names of states -- names = [] for j in xrange(N): name = copy( random.choice( states.values() )) names.append(name) print "%d strings in mem: %s" % (N, nid(names) ) # 10 ids, even with copy() # list to a file, back again -- each string is allocated anew joinsplit = "\n".join(names).split() # same as > file > mem again assert joinsplit == names print "%d strings from a file: %s" % (N, nid(joinsplit) ) # 10000 strings in mem: 10 ids 42149 pickle len # 10000 strings from a file: 10000 ids 188080 pickle len # Python 2.6.4 mac ppc 

新增25jan:
Python内存中有两种string(或任何程序):

  • Ustrings,在一个唯一的string的Ucache:这些节省内存,并使一个== b快,如果两者都在Ucache
  • 其他的可能被存储任何次数的Ostrings。

intern(astring)在Ucache(Alex +1)中放入astring; 除此之外,我们什么也不知道Python如何将Ostrings移到Ucache – “ab”之后“a”+“b”是如何进入的? (“来自文件的string”是没有意义的 – 没有办法知道。)
总之,Ucaches(可能有几个)仍然是黑暗的。

一个历史的脚注: SPITBOL独一无二的所有string约。 1970年。

Python语言的每个实现都可以自由地在分配不可变对象(比如string)时进行折衷 – 或者创build一个新的对象,或者find一个现有的对等对象,并且使用一个更多的引用来完成语言的观点看法。 实际上,实际上,现实世界的实现会达到合理的妥协:当定位这样一个对象时,更多地提到一个合适的现有对象是便宜和容易的,只要find一个合适的现有对象(可能是可能不存在)看起来像可能需要很长时间search。

所以,例如,在一个函数中出现多个相同的string字面值(在我所知的所有实现中)都使用“新的对同一对象的引用”策略,因为当构build函数的常量池时,它非常快速且容易避免重复; 但是在单独的函数中这样做可能是一个非常耗时的任务,所以真实世界的实现要么根本不这样做,要么只在一些启发式识别的情况下做到这一点,编译时间(通过search相同的现有常量减慢)与内存消耗(如果新的常量保持不变,则增加)。

我不知道Python的任何实现(或者对于其他具有常量string的语言,比如Java),在从文件中读取数据时,会花费一些时间来识别可能的重复(通过多次引用重用单个对象) – 这似乎不是一个有希望的折衷(在这里,你将支付运行时间 ,而不是编译时间,所以权衡更不吸引力)。 当然,如果你知道(感谢应用程序级别的考虑)这样的不可变对象很大,很容易出现很多重复,你可以很容易地实现你自己的“常量池”策略( 实习生可以帮你做到这一点,但不难推出自己的产品,例如元组,不可变项目,长整型等等)。

我强烈地怀疑,Python的行为与其他许多语言一样 – 在源代码中识别string常量并为这些语言使用公用表,但在dynamic创buildstring时应用相同的规则。 这是有道理的,因为在你的源代码中只有一组有限的string(尽pipePython可以让你dynamic地评估代码),而在程序过程中更有可能创build大量的string。

这个过程通常被称为实习 – 实际上这个页面的外观也被称为实习。

一个侧面说明:了解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 

你的想法是,通过打印两个单独的expression式的ID并注意“它们相等,如果两个expression式必须相等/相等/相同”是错误的 。 单行输出不一定意味着它的所有内容都是在同一时刻创build和/或共存的。

如果您想知道两个对象是否是同一个对象,请直接询问Python(使用is运算符)。

 x = 42 y = 42 x == y #True x is y #True 

在这个交互中,X和Y应该是==(相同的值),但不是(相同的对象),因为我们运行了两个不同的文字expression式。 因为小的整数和string被caching和重用 ,但是告诉我们它们引用同一个对象。

事实上,如果你真的想看看底层的话 ,你可以总是询问Python在标准sys模块中使用getrefcount函数有多less对象的引用返回对象的引用计数。 这种行为反映了Python优化模型执行速度的多种方法之一。

学习Python