2010-04-08 35 views
116

Nhờ có một số người tuyệt vời trên SO, tôi đã phát hiện ra các khả năng được cung cấp bởi collections.defaultdict, đáng chú ý là khả năng đọc và tốc độ. Tôi đã đặt chúng để sử dụng với thành công.Nhiều cấp độ 'collection.defaultdict' trong Python

Bây giờ tôi muốn triển khai ba cấp độ từ điển, hai từ điển hàng đầu là defaultdict và thấp nhất là int. Tôi không tìm được 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ờ làm việc này, 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 mức độ thứ hai defaultdict thuộc loại int, nhưng tôi không tìm thấy nơi nào hoặc cách thực hiện.

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

Bất kỳ đề xuất thanh lịch nào khác?

Thanks pythoneers!

Trả lời

244

Sử dụng:

d = defaultdict(lambda: defaultdict(int)) 

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

+0

Chỉ có vấn đề là nó sẽ không được chọn, nghĩa là 'đa xử lý' không vui khi gửi đi lại. – Noah

+15

@ Noah: Nó sẽ được chọn nếu bạn sử dụng chức năng cấp mô-đun được đặt tên thay vì một lambda. – interjay

+0

tất nhiên, ngớ ngẩn tôi. – Noah

10

Nhìn vào câu trả lời của nosklo here để 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 

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

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

+0

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

+1

@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

3

Theo yêu cầu @ rschwieb cho D['key'] += 1, chúng ta có thể mở rộng trên previous bằng cách ghi đè Ngoài ra bằng cách định nghĩa __add__ phương pháp, để làm điều này cư xử giống như một collections.Counter()

Đầu tiên __missing__ sẽ được gọi để tạo ra một sản phẩm nào mới giá trị sẽ được chuyển vào __add__. Chúng tôi kiểm tra giá trị, đếm trên các giá trị trống là False.

Xem emulating numeric types để 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 phi python, amirite!), Chúng tôi chỉ có thể cung cấp một giá trị mặc định 0 và sau đó cố gắng hoạt động:

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

có nên nâng cao NotImplemented thay vì ValueError không? – spazm

13

Một cách khác để thực hiện một pickleable, lồng defaultdict là sử dụng một đối tượng cục bộ thay vì một lambda:

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

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

"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

Các vấn đề liên quan