¿Puedo hacer un dict de orden por defecto en Python?

Me gustaría combinar OrderedDict() y defaultdict() de collections en un objeto, que será un dict predeterminado ordenado. es posible?

Lo siguiente (usando una versión modificada de esta receta ) funciona para mí:

 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)) 

Aquí hay otra posibilidad, inspirada en el super () Considered Super de Raymond Hettinger , probado en Python 2.7.X y 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 

Si revisa el MRO de la clase (también conocido como help(OrderedDefaultDict) ), verá esto:

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

lo que significa que cuando se inicializa una instancia de OrderedDefaultDict , difiere al init de OrderedDict , pero este a su vez llamará a los métodos del defaultdict antes de llamar a __builtin__.dict , que es precisamente lo que queremos.

Aquí hay otra solución para pensar si su caso de uso es tan simple como el mío y no necesariamente quiere agregar la complejidad de una implementación de clase DefaultOrderedDict a su código.

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

( None es mi valor predeterminado deseado.)

Tenga en cuenta que esta solución no funcionará si uno de sus requisitos es insertar dinámicamente nuevas claves con el valor predeterminado. Una compensación de la simplicidad.

Actualización 3/13/17 – Me enteré de una función de conveniencia para este caso de uso. Igual que el anterior, pero puede omitir las líneas de items = ... y solo:

 od = OrderedDict.fromkeys(keys) 

Salida:

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

Y si sus claves son caracteres únicos, solo puede pasar una cadena:

 OrderedDict.fromkeys('abc') 

Esto tiene el mismo resultado que los dos ejemplos anteriores.

También puede pasar un valor predeterminado como segundo OrderedDict.fromkeys(...) a OrderedDict.fromkeys(...) .

Si desea una solución simple que no requiera una clase, solo puede usar OrderedDict. setdefault ( key, default=None ) OrderedDict. setdefault ( key, default=None ) o OrderedDict. get ( key, default=None ) OrderedDict. get ( key, default=None ) . Si solo obtiene / configura desde algunos lugares, digamos en un bucle, puede simplemente configurar la opción predeterminada.

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

Es incluso más fácil para las listas con setdefault :

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

Pero si lo usa más de unas cuantas veces, probablemente sea mejor configurar una clase, como en las otras respuestas.

Una versión más simple de la respuesta de @zeekay es:

 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 

Una solución simple y elegante basada en @NickBread. Tiene una API ligeramente diferente para establecer la fábrica, pero siempre es bueno tener buenos valores predeterminados.

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

Otro enfoque simple sería utilizar el método de get diccionario.

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

Inspirado por otras respuestas en este hilo, puedes usar algo como:

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

Me gustaría saber si hay algún inconveniente de inicializar otro objeto de la misma clase en el método que falta .

¡Probé el dict de defecto y descubrí que también está ordenado! tal vez fue solo una coincidencia, pero de todos modos puedes usar la función ordenada:

 sorted(s.items()) 

creo que es mas simple