Có cách nào thông minh để chuyển khóa đến default_factory của defaultdict không?


93

Một lớp có một hàm tạo nhận một tham số:

class C(object):
    def __init__(self, v):
        self.v = v
        ...

Ở đâu đó trong mã, sẽ rất hữu ích cho các giá trị trong một lệnh để biết các khóa của chúng.
Tôi muốn sử dụng một sắc lệnh mặc định với khóa được truyền cho các giá trị mặc định mới sinh:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

Bất kỳ đề xuất?

Câu trả lời:


127

Nó hầu như không đủ tiêu chuẩn là thông minh - nhưng phân lớp là bạn của bạn:

class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError( key )
        else:
            ret = self[key] = self.default_factory(key)
            return ret

d = keydefaultdict(C)
d[x] # returns C(x)

16
Đó chính xác là điều xấu xí mà tôi đang cố gắng tránh ... Ngay cả việc sử dụng một câu lệnh đơn giản và kiểm tra sự tồn tại của khóa cũng sạch hơn nhiều.
Benjamin Nitlehoo

1
@Paul: và đây là câu trả lời của bạn. Xấu xí? Nào!
tzot

4
Tôi nghĩ rằng tôi sẽ lấy đoạn mã đó và đặt nó vào mô-đun tiện ích chung được cá nhân hóa của mình để tôi có thể sử dụng nó bất cứ khi nào tôi muốn. Không quá xấu xí theo cách đó ...
weronika

24
+1 Trực tiếp giải quyết câu hỏi của OP và không có vẻ "xấu xí" đối với tôi. Cũng là một câu trả lời tốt vì nhiều dường như không nhận ra rằng defaultdict's __missing__()phương pháp có thể được ghi đè (vì nó có thể trong bất kỳ lớp con của được xây dựng trong dictlớp kể từ phiên bản 2.5).
martineau

7
+1 Toàn bộ mục đích của __missing__ là tùy chỉnh hành vi cho các khóa bị thiếu. Phương pháp dict.setdefault () được đề cập bởi @silentghost cũng sẽ hoạt động (về mặt tích cực, setdefault () ngắn và đã tồn tại; mặt trừ, nó gặp phải các vấn đề về hiệu quả và không ai thực sự thích tên "setdefault") .
Raymond Hettinger

26

Không có.

Việc defaultdicttriển khai không thể được định cấu hình để chuyển phần thiếu keyvào default_factoryhộp. Tùy chọn duy nhất của bạn là triển khai defaultdictlớp con của riêng bạn , như được đề xuất bởi @JochenRitzel ở trên.

Nhưng điều đó không "thông minh" hoặc gần như sạch sẽ như một giải pháp thư viện tiêu chuẩn sẽ là (nếu nó tồn tại). Do đó, câu trả lời ngắn gọn cho câu hỏi có / không của bạn rõ ràng là "Không".

Thật tệ là thư viện chuẩn đang thiếu một công cụ thường xuyên cần thiết như vậy.


Đúng vậy, sẽ là một lựa chọn thiết kế tốt hơn nếu để nhà máy lấy chìa khóa (hàm đơn nguyên thay vì hàm số không). Thật dễ dàng để loại bỏ một đối số khi chúng ta muốn trả về một hằng số.
YvesgereY

6

Tôi không nghĩ bạn cần defaultdictở đây chút nào. Tại sao không chỉ sử dụng dict.setdefaultphương pháp?

>>> d = {}
>>> d.setdefault('p', C('p')).v
'p'

Điều đó tất nhiên sẽ tạo ra nhiều trường hợp C. Trong trường hợp đó là một vấn đề, tôi nghĩ rằng cách tiếp cận đơn giản hơn sẽ làm:

>>> d = {}
>>> if 'e' not in d: d['e'] = C('e')

Nó sẽ nhanh hơn defaultdicthoặc bất kỳ thay thế nào khác theo như tôi có thể thấy.

ETA liên quan đến tốc độ inkiểm tra so với sử dụng điều khoản thử ngoại trừ:

>>> def g():
    d = {}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(g)
0.19638929363557622
>>> def f():
    d = {}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(f)
0.6167065411074759
>>> def k():
    d = {'a': 2}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(k)
0.30074866358404506
>>> def p():
    d = {'a': 2}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(p)
0.28588609450770264

7
Điều này rất lãng phí trong trường hợp d được truy cập nhiều lần và hiếm khi thiếu khóa: C (key) do đó sẽ tạo ra hàng tấn đối tượng không cần thiết để GC thu thập. Ngoài ra, trong trường hợp của tôi, có một vấn đề khác, vì việc tạo các đối tượng C mới rất chậm.
Benjamin Nitlehoo

@Paul: đúng vậy. Tôi sẽ đề xuất sau đó phương pháp đơn giản hơn, hãy xem bản chỉnh sửa của tôi.
SilentGhost

Tôi không chắc nó nhanh hơn defaultdict, nhưng đây là những gì tôi thường làm (xem nhận xét của tôi cho câu trả lời của THC4k). Tôi hy vọng có một cách đơn giản để hack xung quanh thực tế default_factory không có args, để giữ cho mã thanh lịch hơn một chút.
Benjamin Nitlehoo

5
@SilentGhost: Tôi không hiểu - điều này giải quyết vấn đề của OP như thế nào? Tôi nghĩ OP muốn bất kỳ nỗ lực nào để đọc d[key]để trả lại d[key] = C(key)nếu key not in d. Nhưng giải pháp của bạn yêu cầu anh ta phải thực sự đi và đặt d[key]trước? Làm sao anh ta biết được thứ keyanh ta cần?
tối đa

2
Vì setdefault xấu như địa ngục và lệnh default từ collection NÊN chuyển một hàm gốc nhận được khóa. Thật là một cơ hội lãng phí từ các nhà thiết kế Python!
jgomo3 23/07/18

0

Đây là một ví dụ hoạt động của từ điển tự động thêm giá trị. Nhiệm vụ trình diễn trong việc tìm kiếm các tệp trùng lặp trong / usr / include. Lưu ý rằng từ điển tùy chỉnh PathDict chỉ yêu cầu bốn dòng:

class FullPaths:

    def __init__(self,filename):
        self.filename = filename
        self.paths = set()

    def record_path(self,path):
        self.paths.add(path)

class PathDict(dict):

    def __missing__(self, key):
        ret = self[key] = FullPaths(key)
        return ret

if __name__ == "__main__":
    pathdict = PathDict()
    for root, _, files in os.walk('/usr/include'):
        for f in files:
            path = os.path.join(root,f)
            pathdict[f].record_path(path)
    for fullpath in pathdict.values():
        if len(fullpath.paths) > 1:
            print("{} located in {}".format(fullpath.filename,','.join(fullpath.paths)))
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.