Nhiều cấp độ của 'Collection.defaultdict' trong Python


176

Nhờ một số người tuyệt vời về SO, tôi đã khám phá ra những khả năng được cung cấp bởi collections.defaultdict, đáng chú ý là khả năng đọc và tốc độ. Tôi đã đưa chúng vào sử dụng với thành công.

Bây giờ tôi muốn thực hiện ba cấp độ từ điển, hai cấp độ cao nhất và cấp độ defaultdictthấp nhất int. Tôi không tìm thấy cách thích hợp để làm điều này. Đây là nỗ lực của tôi:

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]

Bây giờ điều này hoạt động, nhưng sau đây, đó là hành vi mong muốn, không:

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

Tôi nghi ngờ rằng tôi nên tuyên bố ở đâu đó rằng cấp độ thứ hai defaultdictlà loại int, nhưng tôi đã không tìm thấy ở đâu hoặc làm thế nào để làm như vậy.

Lý do tôi đang sử dụng defaultdictở nơi đầu tiên là để tránh phải khởi tạo từ điển cho mỗi khóa mới.

Bất kỳ đề nghị thanh lịch hơn?

Cảm ơn pythoneers!

Câu trả lời:


341

Sử dụng:

from collections import defaultdict
d = defaultdict(lambda: defaultdict(int))

Điều này sẽ tạo một cái mới defaultdict(int)bất cứ khi nào một khóa mới được truy cập d.


2
Vấn đề duy nhất là nó sẽ không kén chọn, có nghĩa multiprocessinglà không hài lòng về việc gửi chúng qua lại.

19
@ Không: Sẽ rất khó nếu bạn sử dụng hàm cấp mô-đun có tên thay vì lambda.
interjay

4
@ScienceFriction Bất cứ điều gì cụ thể mà bạn cần trợ giúp? Khi d[new_key]được truy cập, nó sẽ gọi lambda sẽ tạo mới defaultdict(int). Và khi d[existing_key][new_key2]được truy cập, một cái mới intsẽ được tạo ra.
interjay

11
Điều này thật tuyệt. Dường như tôi làm mới lời thề hôn nhân của mình với Python hàng ngày.
mVChr

3
Tìm kiếm thêm chi tiết về việc sử dụng phương thức này với multiprocessingvà chức năng cấp mô-đun được đặt tên là gì? Câu hỏi này theo sau.
Trương Bá Chi

32

Một cách khác để tạo một defaultdict lồng nhau, có thể chọn là sử dụng một phần đối tượng thay vì lambda:

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

Điều này sẽ hoạt động vì lớp defaultdict có thể truy cập toàn cầu ở cấp mô-đun:

"Bạn không thể chọn một đối tượng một phần trừ khi hàm [hoặc trong trường hợp này, lớp] nó có thể truy cập toàn cầu ... dưới __name__ của nó (trong __module__)" - Pickling bọc các hàm một phần


12

Nhìn vào câu trả lời của nosklo ở đây để có giải pháp tổng quát hơn.

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

Kiểm tra:

a = AutoVivification()

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

print a

Đầu ra:

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

Cảm ơn bạn đã liên kết @ miles82 (và chỉnh sửa, @voyager). Cách tiếp cận pythonesque và an toàn là như thế nào?
Morlock

2
Thật không may, giải pháp này không bảo tồn phần đẹp nhất của defaultdict, đó là khả năng viết một cái gì đó như D ['key'] + = 1 mà không phải lo lắng về sự tồn tại của khóa. Đó là tính năng chính mà tôi sử dụng defaultdict cho ... nhưng tôi có thể tưởng tượng từ điển động sâu cũng khá tiện dụng.
rschwieb

2
@rschwieb bạn có thể thêm sức mạnh để viết + = 1 bằng cách thêm phương thức add .
spazm

5

Theo yêu cầu của @ rschwieb D['key'] += 1, chúng tôi có thể mở rộng trước đó bằng cách ghi đè bổ sung bằng cách xác định__add__ phương thức, để làm cho điều này hoạt động giống như mộtcollections.Counter()

Đầu tiên __missing__sẽ được gọi để tạo một giá trị trống mới, sẽ được chuyển vào __add__. Chúng tôi kiểm tra giá trị, dựa trên các giá trị trống False.

Xem mô phỏng các kiểu số để biết thêm thông tin về ghi đè.

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

Ví dụ:

>>> 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}}}

Thay vì kiểm tra đối số là một Số (rất không phải python, amirite!), Chúng tôi chỉ có thể cung cấp giá trị 0 mặc định và sau đó thử thao tác:

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

những thứ này có nên tăng NotIm Hiện thực hơn là ValueError không?
spazm

5

Đến bữa tiệc muộn, nhưng vì chiều sâu tùy tiện, tôi chỉ thấy mình làm điều gì đó như thế này:

from collections import defaultdict

class DeepDict(defaultdict):
    def __call__(self):
        return DeepDict(self.default_factory)

Thủ thuật ở đây về cơ bản là biến bản DeepDictthân nó thành một nhà máy hợp lệ để xây dựng các giá trị còn thiếu. Bây giờ chúng ta có thể làm những việc như

dd = DeepDict(DeepDict(list))
dd[1][2].extend([3,4])
sum(dd[1][2])  # 7

ddd = DeepDict(DeepDict(DeepDict(list)))
ddd[1][2][3].extend([4,5])
sum(ddd[1][2][3])  # 9

1
def _sub_getitem(self, k):
    try:
        # sub.__class__.__bases__[0]
        real_val = self.__class__.mro()[-2].__getitem__(self, k)
        val = '' if real_val is None else real_val
    except Exception:
        val = ''
        real_val = None
    # isinstance(Avoid,dict)也是true,会一直递归死
    if type(val) in (dict, list, str, tuple):
        val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
        # 重新赋值当前字典键为返回值,当对其赋值时可回溯
        if all([real_val is not None, isinstance(self, (dict, list)), type(k) is not slice]):
            self[k] = val
    return val


def _sub_pop(self, k=-1):
    try:
        val = self.__class__.mro()[-2].pop(self, k)
        val = '' if val is None else val
    except Exception:
        val = ''
    if type(val) in (dict, list, str, tuple):
        val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
    return val


class DefaultDict(dict):
    def __getitem__(self, k):
        return _sub_getitem(self, k)

    def pop(self, k):
        return _sub_pop(self, k)

In[8]: d=DefaultDict()
In[9]: d['a']['b']['c']['d']
Out[9]: ''
In[10]: d['a']="ggggggg"
In[11]: d['a']
Out[11]: 'ggggggg'
In[12]: d['a']['pp']
Out[12]: ''

Không có lỗi nữa. Không có bao nhiêu cấp độ lồng nhau. pop cũng không có lỗi

dd = DefaultDict ({"1": 333333})

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.