2010-01-23 119 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

相關問題