2010-04-08 63 views
116

感謝SO上的一些偉人,我發現了collections.defaultdict提供的可能性,特別是在可讀性和速度方面。我已經把它們用於成功。現在Python中多個級別的'collection.defaultdict'

我想實現三個層次的詞典,兩個頂部的莫過於defaultdict和最低的一個是int。我沒有找到適當的方法來做到這一點。這裏是我的嘗試:

from collections import defaultdict 
d = defaultdict(defaultdict) 
a = [("key1", {"a1":22, "a2":33}), 
    ("key2", {"a1":32, "a2":55}), 
    ("key3", {"a1":43, "a2":44})] 
for i in a: 
    d[i[0]] = i[1] 

現在這個工作,但以下,這是需要的行爲,並不:

d["key4"]["a1"] + 1 

我懷疑,我應該在什麼地方宣佈第二級defaultdict類型爲int,但我沒有找到在哪裏或如何去做。

我首先使用defaultdict的原因是爲了避免爲每個新密鑰初始化字典。

更優雅的建議?

感謝pythoneers!

回答

244

用途:

d = defaultdict(lambda: defaultdict(int)) 

每當一個新的關鍵是在d訪問。這將創建一個新defaultdict(int)

+0

唯一的問題是它不會pickle,這意味着'multiprocessing'不喜歡發送這些來回。 – Noah 2012-03-27 16:49:32

+15

@Noah:如果你使用一個命名的模塊級函數而不是lambda表達式,它會醃製。 – interjay 2012-03-27 17:28:38

+0

當然,傻了我。 – Noah 2012-03-27 19:16:38

10

查看nosklo的回答here以獲得更通用的解決方案。

class AutoVivification(dict): 
    """Implementation of perl's autovivification feature.""" 
    def __getitem__(self, item): 
     try: 
      return dict.__getitem__(self, item) 
     except KeyError: 
      value = self[item] = type(self)() 
      return value 

Testing:

a = AutoVivification() 

a[1][2][3] = 4 
a[1][3][3] = 5 
a[1][2]['test'] = 6 

print a 

Output:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}} 
+0

感謝鏈接@ miles82(和編輯,@voyager)。 pythonesque和安全的是這種方法? – Morlock 2010-04-08 14:57:16

+0

不幸的是,這個解決方案並沒有保留defaultdict中最簡單的部分,這就是寫D +'key'] + = 1之類的東西的權力,而不用擔心密鑰的存在。這是我使用defaultdict的主要功能......但我可以想象,動態加深字典也非常方便。 – rschwieb 2014-03-25 00:21:40

+1

@rschwieb您可以通過添加__add__方法添加寫入+ = 1的權力。 – spazm 2014-08-21 21:54:14

3

按@ rschwieb對D['key'] += 1要求,我們可以通過定義__add__法,覆蓋除上previous擴大,使這個行爲更像一個collections.Counter()

首先__missing__將被調用來創建一個新的空價值,這將被傳遞到__add__。我們測試該值,計算空值爲False

有關覆蓋的更多信息,請參閱emulating numeric types

from numbers import Number 


class autovivify(dict): 
    def __missing__(self, key): 
     value = self[key] = type(self)() 
     return value 

    def __add__(self, x): 
     """ override addition for numeric types when self is empty """ 
     if not self and isinstance(x, Number): 
      return x 
     raise ValueError 

    def __sub__(self, x): 
     if not self and isinstance(x, Number): 
      return -1 * x 
     raise ValueError 

例子:

>>> import autovivify 
>>> a = autovivify.autovivify() 
>>> a 
{} 
>>> a[2] 
{} 
>>> a 
{2: {}} 
>>> a[4] += 1 
>>> a[5][3][2] -= 1 
>>> a 
{2: {}, 4: 1, 5: {3: {2: -1}}} 

而不是檢查參數爲數值(非常不蟒蛇,amirite!)我們可以只提供一個默認值0,然後嘗試操作:

class av2(dict): 
    def __missing__(self, key): 
     value = self[key] = type(self)() 
     return value 

    def __add__(self, x): 
     """ override addition when self is empty """ 
     if not self: 
      return 0 + x 
     raise ValueError 

    def __sub__(self, x): 
     """ override subtraction when self is empty """ 
     if not self: 
      return 0 - x 
     raise ValueError 
+0

應該這些提出NotImplemented而不是ValueError? – spazm 2014-08-25 22:41:41

13

另一種方法,使一個與pickle,嵌套defaultdict是使用,而不是一個拉姆達部分對象:

from functools import partial 
... 
d = defaultdict(partial(defaultdict, int)) 

這將工作,因爲defaultdict類可以在模塊級別全局訪問:

"You can't pickle a partial object unless the function [or in this case, class] it wraps is globally accessible ... under its __name__ (within its __module__)" -- Pickling wrapped partial functions