wake-up-neo.net

Kann ich in Python ein geordnetes Standarddiktat erstellen?

Ich möchte OrderedDict() und defaultdict() aus collections in einem Objekt kombinieren, das ein geordnetes Standarddict sein soll. Ist das möglich?

158
jlconlin

Folgendes (mit einer modifizierten Version von dieses Rezept ) funktioniert für mich:

from collections import OrderedDict, Callable

class DefaultOrderedDict(OrderedDict):
    # Source: http://stackoverflow.com/a/6190500/562769
    def __init__(self, default_factory=None, *a, **kw):
        if (default_factory is not None and
           not isinstance(default_factory, Callable)):
            raise TypeError('first argument must be callable')
        OrderedDict.__init__(self, *a, **kw)
        self.default_factory = default_factory

    def __getitem__(self, key):
        try:
            return OrderedDict.__getitem__(self, key)
        except KeyError:
            return self.__missing__(key)

    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        self[key] = value = self.default_factory()
        return value

    def __reduce__(self):
        if self.default_factory is None:
            args = Tuple()
        else:
            args = self.default_factory,
        return type(self), args, None, None, self.items()

    def copy(self):
        return self.__copy__()

    def __copy__(self):
        return type(self)(self.default_factory, self)

    def __deepcopy__(self, memo):
        import copy
        return type(self)(self.default_factory,
                          copy.deepcopy(self.items()))

    def __repr__(self):
        return 'OrderedDefaultDict(%s, %s)' % (self.default_factory,
                                               OrderedDict.__repr__(self))
81
zeekay

Hier ist eine weitere Möglichkeit, inspiriert von Raymond Hettingers super () als Super , getestet auf Python 2.7.X und 3.4.X:

from collections import OrderedDict, defaultdict

class OrderedDefaultDict(OrderedDict, defaultdict):
    def __init__(self, default_factory=None, *args, **kwargs):
        #in python3 you can omit the args to super
        super(OrderedDefaultDict, self).__init__(*args, **kwargs)
        self.default_factory = default_factory

Wenn Sie sich die MRO der Klasse (aka help(OrderedDefaultDict)) ansehen, werden Sie Folgendes sehen:

class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict)
 |  Method resolution order:
 |      OrderedDefaultDict
 |      collections.OrderedDict
 |      collections.defaultdict
 |      __builtin__.dict
 |      __builtin__.object

das heißt, wenn eine Instanz von OrderedDefaultDict initialisiert wird, verschiebt sie sich zum Init der OrderedDict, aber diese Instanz ruft die Methoden der defaultdict auf, bevor __builtin__.dict aufgerufen wird.

36
avyfain

Hier ist eine weitere Lösung, über die Sie nachdenken sollten, ob Ihr Anwendungsfall so einfach ist wie meiner und Sie nicht unbedingt die Komplexität einer DefaultOrderedDict-Klassenimplementierung in Ihren Code einfügen möchten.

from collections import OrderedDict

keys = ['a', 'b', 'c']
items = [(key, None) for key in keys]
od = OrderedDict(items)

(None ist mein gewünschter Standardwert.)

Beachten Sie, dass diese Lösung nicht funktioniert, wenn eine Ihrer Anforderungen das dynamische Einfügen neuer Schlüssel mit dem Standardwert ist. Ein Kompromiss der Einfachheit. 

Update 3/13/17 - Ich habe von einer Komfortfunktion für diesen Anwendungsfall erfahren. Wie oben, aber Sie können die Zeile items = ... weglassen und nur:

od = OrderedDict.fromkeys(keys)

Ausgabe:

OrderedDict([('a', None), ('b', None), ('c', None)])

Wenn Ihre Schlüssel aus einzelnen Zeichen bestehen, können Sie einfach eine Zeichenfolge übergeben:

OrderedDict.fromkeys('abc')

Dies hat dieselbe Ausgabe wie die beiden obigen Beispiele.

Sie können auch einen Standardwert als zweites Argument an OrderedDict.fromkeys(...) übergeben.

19
Taylor Edmiston

Wenn Sie eine einfache Lösung wünschen, für die keine Klasse erforderlich ist, können Sie einfach OrderedDict.setdefault(key, default=None) oder OrderedDict.get(key, default=None) verwenden. Wenn Sie nur von ein paar Stellen aus zugreifen/einstellen, beispielsweise in einer Schleife, können Sie einfach die Standardeinstellung festlegen.

totals = collections.OrderedDict()

for i, x in some_generator():
    totals[i] = totals.get(i, 0) + x

Für Listen mit setdefault ist es noch einfacher:

agglomerate = collections.OrderedDict()

for i, x in some_generator():
    agglomerate.setdefault(i, []).append(x)

Wenn Sie es jedoch mehr als ein paar Mal verwenden, ist es wahrscheinlich besser, eine Klasse einzurichten, wie in den anderen Antworten.

14
Artyer

Eine einfachere Version von @zeekays Antwort lautet:

from collections import OrderedDict

class OrderedDefaultListDict(OrderedDict): #name according to default
    def __missing__(self, key):
        self[key] = value = [] #change to whatever default you want
        return value
5
Neck Beard

Ein anderer einfacher Ansatz wäre die Verwendung der get-Methode

>>> from collections import OrderedDict
>>> d = OrderedDict()
>>> d['key'] = d.get('key', 0) + 1
>>> d['key'] = d.get('key', 0) + 1
>>> d
OrderedDict([('key', 2)])
>>> 
2
snebel29

Eine einfache und elegante Lösung, die auf @NickBread aufbaut. Hat eine etwas andere API, um die Factory einzustellen, aber gute Standardwerte sind immer schön.

class OrderedDefaultDict(OrderedDict):
    factory = list

    def __missing__(self, key):
        self[key] = value = self.factory()
        return value
1
F Pereira

Inspiriert von anderen Antworten in diesem Thread, können Sie Folgendes verwenden:

from collections import OrderedDict

class OrderedDefaultDict(OrderedDict):
    def __missing__(self, key):
        value = OrderedDefaultDict()
        self[key] = value
        return value

Ich würde gerne wissen, ob es Nachteile gibt, ein anderes Objekt derselben Klasse in der missing -Methode zu initialisieren. 

0
Samarth