10

nỗ lực đầu tiên của tôi kết hợp các tính năng của hai bộ từ điển trong các mô-đun collections là tạo ra một lớp kế thừa họ:Tại sao tôi không thể tạo dict mặc định, đặt hàng bằng cách kế thừa OrderedDict và defaultdict?

from collections import OrderedDict, defaultdict 

class DefaultOrderedDict(defaultdict, OrderedDict): 
    def __init__(self, default_factory=None, *a, **kw): 
     super().__init__(default_factory, *a, **kw) 

Tuy nhiên, tôi không thể gán một mục vào từ điển này:

d = DefaultOrderedDict(lambda: 0) 
d['a'] = 1 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "/usr/lib64/python3.3/collections/__init__.py", line 64, in __setitem__ 
    self.__map[key] = link = Link() 
AttributeError: 'DefaultOrderedDict' object has no attribute '_OrderedDict__map' 

Thật vậy, this question about how to create a similar object có câu trả lời đạt được nó bằng cách mở rộng lớp OrderedDict và tự thực hiện lại các phương thức bổ sung được cung cấp defaultdict. Sử dụng nhiều thừa kế sẽ được sạch hơn. Tại sao nó không hoạt động?

+0

bạn có thể sử dụng 'defaultdict (int)' thay vì 'defaultdict (lambda: 0)'. –

Trả lời

3

Nguyên nhân là do các init method of defaultdict thay vì gọi __init__ của lớp tiếp theo trong MRO gọi init của PyDict_Type do đó một số các thuộc tính như __map được đặt trong OrderedDict's __init__ không bao giờ được khởi tạo, vì thế mà lỗi.

>>> DefaultOrderedDict.mro() 
[<class '__main__.DefaultOrderedDict'>, 
<class 'collections.defaultdict'>, 
<class 'collections.OrderedDict'>, 
<class 'dict'>, <class 'object'>] 

defaultdict không có __setitem__ phương pháp riêng của họ:

>>> defaultdict.__setitem__ 
<slot wrapper '__setitem__' of 'dict' objects> 
>>> dict.__setitem__ 
<slot wrapper '__setitem__' of 'dict' objects> 
>>> OrderedDict.__setitem__ 
<unbound method OrderedDict.__setitem__> 

Vì vậy, khi bạn gọi d['a'] = 1, để tìm kiếm __setitem__ Python đạt OrdereredDict của __setitem__ và họ tiếp cận của uninitialized __map thuộc tính đã nêu ra lỗi:


Sửa chữa sẽ là gọi __init__ trên cả defaultdictOrderedDict một cách rõ ràng:

class DefaultOrderedDict(defaultdict, OrderedDict): 
    def __init__(self, default_factory=None, *a, **kw): 
     for cls in DefaultOrderedDict.mro()[1:-2]: 
      cls.__init__(self, *a, **kw) 
+1

Tại sao bạn không thể đổi thứ tự của các lớp cơ sở: 'class OrderedDefaultDict (OrderedDict, defaultdict):', và sau đó có '__init __ (self, default_factory = None, * args, ** kwargs)' chứa hai dòng : 'super (OrderedDefaultDict, self) .__ init __ (* args, ** kwargs)' và 'self.default_dict = default_dict'? – Sam

+1

Tôi vừa thử đề nghị của Sam và nó hoạt động. (Vâng, sau khi tôi đã thay đổi default_dict thành default_factory!) – samwyse

4

Có lẽ bạn đang đến từ một nền tảng Java, nhưng đa kế thừa không làm những gì bạn mong đợi nó bằng Python. Gọi super từ số init của mặc địnhOrderedDict gọi super() là init của defaultdict và không bao giờ là init của OrderedDict. Thuộc tính bản đồ lần đầu tiên được xác định trong hàm __init của OrderedDict. Việc thực hiện như sau (từ nguồn):

def __init__(self, *args, **kwds): 
    '''Initialize an ordered dictionary. The signature is the same as 
    regular dictionaries, but keyword arguments are not recommended because 
    their insertion order is arbitrary. 

    ''' 
    if len(args) > 1: 
     raise TypeError('expected at most 1 arguments, got %d' % len(args)) 
    try: 
     self.__root 
    except AttributeError: 
     self.__root = root = []      # sentinel node 
     root[:] = [root, root, None] 
     self.__map = {} 
    self.__update(*args, **kwds) 

Lưu ý rằng điều này không liên quan đến thuộc tính riêng tư. Một ví dụ tối thiểu với nhiều thừa kế có thể minh họa điều này:

class Foo: 
    def __init__(self): 
     self.foo=2 

class Bar: 
    def __init__(self): 
     self.bar=1 

class FooBar(Foo,Bar): 
    def __init__(self): 
     super().__init__() 

fb = FooBar() 

fb.foo 
>>2 
fb.bar 
>>AttributeError: 'FooBar' object has no attribute 'bar' 

Vì vậy, nhà xây dựng của Bar chưa bao giờ được gọi. Lệnh phân giải phương thức Pythons đi từ trái sang phải cho đến khi nó tìm thấy một lớp với tên hàm mà nó tìm kiếm (trong trường hợp này là init) và bỏ qua tất cả các lớp khác ở bên phải (trong trường hợp này là Bar)

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