example - ordereddict python 3
MĂșltiples niveles de ''collection.defaultdict'' en Python (4)
De acuerdo con la solicitud de @ D[''key''] += 1
__add__
, podemos ampliar el previous anulando la suma definiendo el método __add__
, para hacer que este comportamiento se parezca más a un collections.Counter()
Primero __missing__
se llamará para crear un nuevo valor vacío, que se pasará a __add__
. Probamos el valor, contando con que los valores vacíos son False
.
Vea emular tipos numéricos para más información sobre anulación.
from numbers import Number
class autovivify(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
def __add__(self, x):
""" override addition for numeric types when self is empty """
if not self and isinstance(x, Number):
return x
raise ValueError
def __sub__(self, x):
if not self and isinstance(x, Number):
return -1 * x
raise ValueError
Ejemplos:
>>> import autovivify
>>> a = autovivify.autovivify()
>>> a
{}
>>> a[2]
{}
>>> a
{2: {}}
>>> a[4] += 1
>>> a[5][3][2] -= 1
>>> a
{2: {}, 4: 1, 5: {3: {2: -1}}}
En lugar de verificar que el argumento es un Número (¡muy distinto de Python, amirite!) Podríamos simplemente proporcionar un valor predeterminado de 0 y luego intentar la operación:
class av2(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
def __add__(self, x):
""" override addition when self is empty """
if not self:
return 0 + x
raise ValueError
def __sub__(self, x):
""" override subtraction when self is empty """
if not self:
return 0 - x
raise ValueError
Gracias a algunas personas geniales en SO, descubrí las posibilidades ofrecidas por collections.defaultdict
, especialmente en legibilidad y velocidad. Los puse en uso con éxito.
Ahora me gustaría implementar tres niveles de diccionarios, los dos superiores son defaultdict
y el más bajo es int
. No encuentro la manera apropiada de hacer esto. Aquí está mi intento:
from collections import defaultdict
d = defaultdict(defaultdict)
a = [("key1", {"a1":22, "a2":33}),
("key2", {"a1":32, "a2":55}),
("key3", {"a1":43, "a2":44})]
for i in a:
d[i[0]] = i[1]
Ahora esto funciona, pero el siguiente, que es el comportamiento deseado, no:
d["key4"]["a1"] + 1
Sospecho que debería haber declarado en alguna parte que el defaultdict
segundo nivel es de tipo int
, pero no encontré dónde ni cómo hacerlo.
La razón por la que estoy usando defaultdict
es evitar tener que inicializar el diccionario para cada nueva clave.
¿Alguna sugerencia más elegante?
Gracias Pitoneros!
Mire la respuesta de nosklo here para una solución más general.
class AutoVivification(dict): """Implementation of perl''s autovivification feature.""" def __getitem__(self, item): try: return dict.__getitem__(self, item) except KeyError: value = self[item] = type(self)() return value
Pruebas:
a = AutoVivification() a[1][2][3] = 4 a[1][3][3] = 5 a[1][2][''test''] = 6 print a
Salida:
{1: {2: {''test'': 6, 3: 4}, 3: {3: 5}}}
Otra forma de crear un incumplimiento predefinido anidable es utilizar un objeto parcial en lugar de un lambda:
from functools import partial
...
d = defaultdict(partial(defaultdict, int))
Esto funcionará porque la clase defaultdict es accesible globalmente a nivel de módulo:
"No se puede recortar un objeto parcial a menos que la función [o en este caso, la clase] que envuelve esté accesible globalmente ... bajo su __name__ (dentro de su __module__)" - Funciones parciales envueltas en decapado
Utilizar:
d = defaultdict(lambda: defaultdict(int))
Esto creará un nuevo defaultdict(int)
siempre que se acceda a una nueva clave en d
.