2010-01-23 124 views
29

具有相同字符的两个Python字符串,a == b, 可以共享内存,id(a)== id(b), 或者可能在内存中两次,id(a)!= id(b)。 尝试Python何时为相同的字符串分配新内存?

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

这里的Python认识到新创建的“a” +“b”的相同 为“AB”已在内存中 - 不坏。

现在考虑一个N长的州名称列表 [“Arizona”,“Alaska”,“Alaska”,“California”...] (在我的情况下,N〜500000)。
我看到50个不同的id()s ⇒每个字符串“Arizona”...只存储一次,很好。
但是将列表写入磁盘并再次读回: “相同”列表现在具有N个不同的id(),方式更多的内存,请参见下文。

怎么回事 - 任何人都可以解释Python字符串内存分配?

""" 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内存2种串(或任何程序):

  • Ustrings,在唯一的字符串的Ucache:这些节省内存,并做出= = b快,如果两者都在Ucache中
  • Ostrings,其他可能会被存储多次。

intern(astring)将astring放在Ucache(Alex +1)中; 除了我们什么都不知道Python如何将Ostrings移到Ucache - “ab”之后“a”+“b”是如何进入的? (“文件中的字符串”没有意义 - 没有办法知道。)
简而言之,Ucaches(可能有几个)仍然不明朗。

一个历史注脚: SPITBOL 独特的所有字符串约。 1970年

回答

36

Python语言的每个实施是自由作出自己的取舍在分配不可变对象(如字符串) - 无论是制作一个新的,或发现现有等于1并且使用一个多参考吧,从语言的角度来看,这很好。在实践中,当然,真实世界的实现会达到合理的妥协:当定位这样一个对象时,更多地提到一个合适的现有对象是便宜和容易的,只要找到一个合适的现有对象(可能可能不存在)看起来可能需要很长时间搜索。因此,例如,在单个函数中出现多个相同的字符串字面值(在我所知的所有实现中)都使用“对同一对象的新引用”策略,因为在构建该函数的常量池时它很漂亮快速且容易避免重复;但是在之间单独执行函数可能是一项非常耗时的任务,所以真实世界的实现要么根本不这样做,要么只在一些启发式识别的案例子集中进行,在这种情况下人们可以希望得到一个合理的编译时间的折衷(通过搜索相同的现有常量减慢)与内存消耗(如果新的常量持续不断增加)。

我不知道任何Python的实现(或者对于那些常量字符串的其他语言,比如Java),在读取数据时会花费一些时间来识别可能的重复项(通过多次引用重用一个对象)从一个文件 - 它似乎不是一个有前途的折衷(在这里你会支付运行时,而不是编译时间,所以权衡更不吸引力)。当然,如果您知道(感谢应用程序级别的考虑)这些不可变对象很大并且很容易出现很多重复,那么您可以很容易地实现自己的“常量池”策略(intern可以帮助您为字符串执行操作,但不难推出自己的产品,例如带有不可变项目的元组,长整型等等)。

+0

我的答案中有什么有价值的东西,你不认为是你的?如果不是,我会删除我的答案。如果有的话,你想编辑它到你的和*然后*我会删除我的答案? – 2010-01-23 17:54:23

+0

+1提到'实习生'。我完全忘记了这个功能的存在。在\ n“.join(names).split()]中使用'joinsplit = [intern(n)'来完成这项工作,并将我的MacBook上的内存使用量从4,374,528降低到3,190,783。 – 2010-01-23 18:20:28

+0

@John,我认为有两个观点(我从一个“内幕人士的角度来看”,你是一位经验丰富的程序员,对Python没有特别的“内幕人士的观点”)是非常有价值的 - 不确定是否有最佳的方式来获得同一个“三角测量”在一个单一的答案! – 2010-01-23 18:36:33

16

我强烈怀疑的Python的行为就像许多其他语言在这里 - 识别字符串常量你的源代码内,并使用公共表那些,但动态创建的字符串时应用相同的规则。这很有意义,因为在你的源代码中只有一组有限的字符串(尽管Python可以让你动态地评估代码),而在程序过程中更有可能创建大量的字符串。

这个过程通常被称为实习 - 实际上看起来this page它也被称为在Python中实习。

+0

任何想法然后为什么id(“ab”)== id(“a”+“b”)? 您是否同意我们只是不知道Python如何运行Ucaches? – denis 2010-01-25 17:43:13

+3

为了完整:表达式“a”+“b”被静态地转换为表达式“ab”,然后被发现与另一个字符串相同。这一切都发生在编译时。 – 2013-11-14 20:34:43

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

在这种互动,X和Y应 ==(值相同),但不为(同一个对象),因为我们跑了两个不同的 文字表述。因为小 整数和字符串缓存和 重复使用,虽然,是告诉我们他们 引用相同的单个对象。

事实上,如果你真的想看看 引擎盖下,你可以随时使用getrefcount 功能标准sys模块 返回对象的引用问 Python中有多少引用有 到对象计数。 这种行为反映了Python为其 执行速度优化其多种方式之一。

Learning Python

10

一个侧面说明:这是非常重要的,知道在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,并指出“他们是平等的ERGO两个表达式必须等于/当量/相同的”是故障。单行输出并不一定意味着其所有内容都是在同一时刻创建和/或共存的。

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

+5

关于这里发生了什么的一些解释:'print id(a + b),id(b + a)'line首先将a和b连接到一个新分配的字符串ab中,然后将它传递给'id',然后将其解除分配,因为它不再需要。然后,“ba”以相同的方式分配,并最终分配到内存中的相同位置(CPython有这样做的习惯)。然后将“ba”传递给'id',它返回相同的结果。然而,在下一行中,“ab”和“ba”都会被传递给'is'运算符,所以它们必须分配在不同的位置。 – javawizard 2012-11-27 02:57:59

相关问题