10

Mein erster Versuch, die Merkmale von zwei Wörterbücher der collections Modul zu verbinden war eine Klasse zu erstellen, die sie erbt:Warum kann ich ein standardmäßiges, geordne- tes Diktat nicht durch Erben von OrderedDict und defaultdict erstellen?

from collections import OrderedDict, defaultdict 

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

Allerdings kann ich ein Element in diesem Wörterbuch nicht abtreten:

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' 

In der Tat hat this question about how to create a similar object Antworten, die es erreichen, indem Sie die Klasse OrderedDict erweitern und die zusätzlichen Methoden, die defaultdict bereitgestellt werden, manuell erneut implementieren. Die Verwendung der Mehrfachvererbung wäre sauberer. Warum funktioniert es nicht?

+0

Sie können 'defaultdict (int)' anstelle von 'defaultdict (lambda: 0)' verwenden. –

Antwort

3

Der Grund dafür ist, dass die init method of defaultdict statt __init__ der nächsten Klasse in MRO ruft init von PyDict_Type daher einige der Attribute wie __map aufrufen, die in OrderedDict's __init__ gesetzt werden nie initialisiert werden, damit der Fehler auf.

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

Und defaultdict keine eigene __setitem__ Methode haben:

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

Also, wenn Sie d['a'] = 1 genannt, auf der Suche nach __setitem__ Python erreicht OrdereredDict der __setitem__ und ihr der Zugang von nicht initialisierten __map Attribute der Fehler wurde ausgelöst:


Ein Fix wird zu sein rufen __init__ auf beide defaultdict und OrderedDict ausdrücklich:

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

Warum können Sie nicht einfach die Reihenfolge der Basisklassen vertauschen: 'class OrderedDefaultDict (OrderedDict, defaultdict):', und dann haben Sie Ihre '__init __ (self, default_factory = Keine, * args, ** kwargs)' zwei Zeilen : 'super (OrderedDefaultDict, self) .__ init__ (* args, ** kwargs)' und 'self.default_dict = default_dict'? – Sam

+1

Ich habe gerade Sams Vorschlag versucht und es hat funktioniert. (Nun, nachdem ich default_dict in default_factory geändert habe!) – samwyse

4

Vielleicht sind Sie von einem Java-Hintergrund kommen, aber die Mehrfachvererbung nicht tut, was man erwarten würde es in Python tut. Aufruf Super von der init der defaultOrderedDict ruft die super() als init defaultdict und nie die init von OrderedDict. Das Kartenattribut wird zuerst in der Funktion __init von OrderedDict definiert. Die Implementierung ist die folgende (aus der Quelle):

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) 

Beachten Sie, dass dies nicht mit dem Attribut privat sein muss. Ein minimales Beispiel mit Mehrfachvererbung kann dies verdeutlichen:

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' 

der Konstruktor von Bar So wurde nie genannt. Die Lösungsreihenfolge der Pythons-Methode geht von links nach rechts, bis sie eine Klasse mit dem gesuchten Funktionsnamen findet (in diesem Fall init) und ignoriert dann alle anderen Klassen rechts (in diesem Fall Bar)

Verwandte Themen