¿Cómo hago la automatización avanzada de hash de Python?

Esta pregunta trata sobre la implementación de la autovivificación de Perl completa en Python. Sé que se hicieron preguntas similares antes y hasta ahora la mejor respuesta está en ” ¿Cuál es la mejor manera de implementar diccionarios nesteds en Python? “. Sin embargo, estoy buscando hacer esto:

a['x']['y'].append('z') 

sin declarar a['x']['y'] = [] primero, o mejor dicho, sin declarar a['x'] = {} tampoco. (Tenga en cuenta que en Perl puede hacer push @{$a->{x}{y}}, 'z'; )

Sé que las clases de dict y de la list no se mezclan, así que esto es difícil, pero me interesa ver si alguien tiene una solución ingeniosa que involucre la creación de una clase heredada de dict pero ¿define un nuevo método de append ?

También sé que esto podría deshacerse de algunos puristas de Python que me pedirán que me quede con Perl. Pero, aunque solo sea por un desafío, me gustaría ver algo.

 a = collections.defaultdict(lambda: collections.defaultdict(list)) 

Quizás esto resuelva su necesidad de cualquier número de “dimensiones” en su diccionario:

 a= collections.defaultdict(list) 

El único cambio en su código es:

 a['x', 'y'].append('z') 

Por supuesto, esta solución es la que usted desea, depende de dos condiciones:

  1. si necesita acceder fácilmente a todas las listas con, por ejemplo, “x” en la “primera dimensión”
  2. Si estás pegado a la forma en que Perl mágicamente te agrada más 🙂

Si cualquiera de estas dos condiciones es cierta, mi solución no te ayudará.

Como no sabemos de antemano si necesitamos un diccionario o una lista, entonces no puede combinar la autovivificación con listas. A menos que, Trabajando con la respuesta de Nosklo de la pregunta vinculada, agregue una lista de “características” al diccionario subyacente. Básicamente, asumiendo un orden de “clasificación” para las claves, y siempre usándolo con los métodos de lista. He hecho esto como un ejemplo:

 class AutoVivification(dict): """Implementation of perl's autovivification feature. Has features from both dicts and lists, dynamically generates new subitems as needed, and allows for working (somewhat) as a basic type. """ def __getitem__(self, item): if isinstance(item, slice): d = AutoVivification() items = sorted(self.iteritems(), reverse=True) k,v = items.pop(0) while 1: if (item.start < k < item.stop): d[k] = v elif k > item.stop: break if item.step: for x in range(item.step): k,v = items.pop(0) else: k,v = items.pop(0) return d try: return dict.__getitem__(self, item) except KeyError: value = self[item] = type(self)() return value def __add__(self, other): """If attempting addition, use our length as the 'value'.""" return len(self) + other def __radd__(self, other): """If the other type does not support addition with us, this addition method will be tried.""" return len(self) + other def append(self, item): """Add the item to the dict, giving it a higher integer key than any currently in use.""" largestKey = sorted(self.keys())[-1] if isinstance(largestKey, str): self.__setitem__(0, item) elif isinstance(largestKey, int): self.__setitem__(largestKey+1, item) def count(self, item): """Count the number of keys with the specified item.""" return sum([1 for x in self.items() if x == item]) def __eq__(self, other): """od.__eq__(y) <==> od==y. Comparison to another AV is order-sensitive while comparison to a regular mapping is order-insensitive. """ if isinstance(other, AutoVivification): return len(self)==len(other) and self.items() == other.items() return dict.__eq__(self, other) def __ne__(self, other): """od.__ne__(y) <==> od!=y""" return not self == other 

Esto sigue la característica básica de autovivificación de generarse dinámicamente para las claves de fallo. Sin embargo, también implementa algunos de los métodos enumerados aquí . Esto le permite actuar como una cosa de casi lista / dictado.

Para el rest de las características de la lista, agregue los métodos enumerados. Lo estoy tratando como un diccionario con los métodos de lista. Si se llama a un método de lista, entonces se hace una suposición sobre el orden de los elementos retenidos, es decir, que las cadenas tienen un orden inferior a los enteros, y que las claves están siempre en orden “ordenado”.

También admite la adición, como ejemplo de estos métodos . Esto viene de mi propio caso de uso. Necesitaba agregar elementos de un diccionario AutoVivified, pero si no existe, se crea y se devuelve un nuevo objeto de AutoVivification . No tienen un “valor” entero, por lo que no puedes hacer esto:

 rp = AutoVivification() rp['a']['b'] = 3 rp['a']['b'] + rp['q'] 

Eso anula el propósito, ya que no sé si algo va a estar allí, pero de todos modos quiero un valor predeterminado. Así que le he agregado los métodos __add__ y __radd__ . Utilizan la length del diccionario subyacente como valor integer , por lo que un objeto AV recién creado tiene un valor de cero para la adición. Si una clave tiene algo más que un objeto AV, entonces obtenemos el método de adición de esa cosa, si se implementa.

Simplemente ampliando la respuesta de Ignacio para presentar algunas opciones adicionales que te permiten solicitar explícitamente más comportamiento mágico de los diccionarios de Python. La capacidad de mantenimiento del código escrito de esta manera sigue siendo dudosa, pero quería dejar claro que se trata de “¿Es posible mantener este tipo de estructura de datos?” (Tengo mis dudas) no “¿Se puede hacer que Python se comporte de esta manera?” (ciertamente puede).

Para admitir niveles arbitrarios de anidamiento para el aspecto del espacio de nombres, todo lo que tiene que hacer es nombrar la función (en lugar de usar lambda) y hacerla auto-referencial:

 >>> from collections import defaultdict >>> def autodict(): return defaultdict(autodict) ... >>> a = autodict() >>> a[1][2][3] = [] >>> type(a[1])  >>> type(a[1][2])  >>> type(a[1][2][3])  >>> a[1][2][3] [] 

Sin embargo, esto introduce el “problema” que tiene que establecer una lista explícitamente antes de poder agregarla. La respuesta de Python a eso está en el método setdefault , que en realidad ha existido por más tiempo que collections.defaultdict :

 >>> a.setdefault(3, []).append(10) >>> a.setdefault(3, []).append(11) >>> a[3] [10, 11] >>> a[2].setdefault(3, []).append(12) >>> a[2].setdefault(3, []).append(13) >>> a[2][3] [12, 13] >>> a[1][2].setdefault(3, []).append(14) >>> a[1][2].setdefault(3, []).append(15) >>> a[1][2][3] [14, 15] 

Todas las collections.defaultdict realmente hacen que el caso común en el que siempre pase el mismo segundo parámetro para dict.setdefault sea ​​mucho más fácil de usar. Para casos más complejos, como este, aún puede usar dict.setdefault directamente para los aspectos que las collections.defaultdict no pueden manejar.