2014-01-29 13 views
2

我試圖定義一個函數來創建一個兩層的字典,所以它應該產生的格式爲什麼兩級字典的值都指向Python 2.7中的同一個對象?

dict = {tier1:{tier2:value}}. 

的代碼是:

def two_tier_dict_init(tier1,tier2,value): 
    dict_name = {} 
    for t1 in tier1: 
     dict_name[t1] = {} 
     for t2 in tier2: 
      dict_name[t1][t2] = value 
    return dict_name 

下面這個例子......

tier1 = ["foo","bar"] 
tier2 = ["x","y"] 
value = [] 
foobar_dict = two_tier_dict_init(tier1,tier2,value) 
在它的面前

產生我想要的東西:

foobar_dict = {'foo':{'x': [],'y':[]}, 
       'bar':{'x': [],'y':[]}}     } 

然而,附加像

foobar_dict["foo"]["x"].append("thing") 

任何值時,所有的數值得到附加這樣的結果是:

foobar_dict = {'foo':{'x': ["thing"],'y':["thing"]}, 
       'bar':{'x': ["thing"],'y':["thing"]}} 

起初我以爲,由於方式我定義構建字典,所有值指向內存中的相同空間,但我無法弄清楚爲什麼應該如此。後來我發現,如果我從一個空表的值更改爲整數,因此當我這樣做,

foobar_dict["foo"]["x"] +=1 

只有所需的值被改變。

因此,我必須得出結論,這是與list.append方法有關,但我無法弄清楚。什麼是解釋?

N.B.我需要這個功能來構建大型字典,每個字典都有數百個元素。我也用同樣的方法構建了一個三層版本,並且出現了相同的問題。

回答

6

您只通過一個列表對象,而您的第二層字典僅存儲對該一個對象的引用。

如果您需要存儲不同的列表,則需要爲每個條目創建一個新列表。你可以使用一個工廠函數爲:

def two_tier_dict_init(tier1, tier2, value_factory): 
    dict_name = {} 
    for t1 in tier1: 
     dict_name[t1] = {} 
     for t2 in tier2: 
      dict_name[t1][t2] = value_factory() 
    return dict_name 

然後使用:

two_tier_dict_init(tier1, tier2, list) 

有它創建空列表。您可以使用任何可調用的價值工廠裏,包括lambda,如果你想存儲不可變對象如字符串或整數:

two_tier_dict_init(tier1, tier2, lambda: "I am shared but immutable") 

你可以使用字典理解,以簡化你的函數:

def two_tier_dict_init(tier1, tier2, value_factory): 
    return {t1: {t2: value_factory() for t2 in tier2} for t1 in tier1} 
+0

我喜歡工廠的方法(+1)。 – NPE

+0

這一切都有道理,謝謝指出,value_factory方法是完美的。優秀的迴應 –

0

所有的值都指向同一個列表對象。當您在該列表對象上調用append()時,所有字典值都會同時發生更改。

要創建列表改變的副本

 dict_name[t1][t2] = value 

 dict_name[t1][t2] = value[:] 

 dict_name[t1][t2] = copy.deepcopy(value) 

前者將使淺(即一個級別)的副本,後者會做一個深層次的複製。

1

發生這種情況的原因是,所有第二層字典都使用與值相同的列表填充,並且所有條目都指向相同的列表對象。

一種解決方案是在列表中的每個屬性的複製:

dict_name [T1] [T2] =值[:]

這隻有當你確信值始終是清單的作品。

另一種更通用的解決方案,即與任何對象,包括嵌套列表和字典,工作原理是深度複製:

dict_name [T1] [T2] = copy.deepcopy(值)

如果用類似數字或字符串的不可變對象填充字符串,內部所有條目也會引用同一個對象,但不希望出現的效果不會發生,因爲數字和字符串是不可變的。

0

這似乎與int整合的原因是因爲它們是不可變的,並且增強的作業(+=和朋友)像普通的賦值語句(它可能會回到同一個對象)那樣進行名稱重新綁定。當你這樣做:

foobar_dict["foo"]["x"] +=1 

你最終用一個不同的替換舊的int對象。 int s沒有能力在原地更改值,因此添加構建(或可能發現,因爲CPython實施了某些整數)與新值有不同的整數。

所以,即使foobar_dict["foo"]["x"]foobar_dict["foo"]["y"]開始了同樣的INT(他們這樣做),加入到他們中的一個,使他們現在包含不同整數。

你可以看到這個區別,如果你嘗試一下用簡單的變量:

>>> a = b = 1 
>>> a is b 
True 
>>> a += 1 
>>> a 
2 
>>> b 
1 

在另一方面,list是可變的,調用append沒有做任何重新綁定。所以,如您所懷疑的,如果foobar_dict["foo"]["x"]foobar_dict["foo"]["y"]是相同的列表(並且它們是 - 與is一起檢查),並且您追加到它,它們仍然是相同的列表。

相關問題