2010-03-12 8 views
10

Esta pregunta se trata de implementar la autovibición completa de Perl en Python. Sé que se hicieron preguntas similares antes y hasta ahora la mejor respuesta está en "What is the best way to implement nested dictionaries in Python?". Sin embargo, estoy buscando para hacer esto:¿Cómo hago la autovibición avanzada de hash de Python?

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

sin declarar a['x']['y'] = [] primera, o más bien, no declarar a['x'] = {} tampoco. (Nota en Perl que puede hacer push @{$a->{x}{y}}, 'z';.)

dict y list clases sorta no se mezclan, por lo que esto es difícil, pero estoy interesado en ver si alguien tiene una solución ingeniosa probablemente implica la creación de una clase heredada desde dict pero definió un nuevo método append en él?

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

Respuesta

16
a = collections.defaultdict(lambda: collections.defaultdict(list)) 
+0

Hermosa solución. Muchas gracias. – Zhang18

+2

Un ejemplo de python que tiene una, y solo una, forma obvia de hacer algo :) – Brian

+0

En realidad, me di cuenta de que esta respuesta solo funciona para 2 niveles de dict y un 3er nivel de lista. ¿Hay alguna manera de ser agnóstico? es decir, después de declarar a, puedo hacer 'a ['x']. append ('z')' o 'a ['x'] ['y'] ['w']. append ('z') ¿? Gracias. – Zhang18

2

Tal vez esto soluciona su necesidad para cualquier número de “dimensiones” en su diccionario:

a= collections.defaultdict(list) 

el único cambio en el código de bienestar:

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

Por supuesto, esta solución siendo el que desea depende de dos condiciones:

  1. si necesita acceder fácilmente a todas las listas, por ej. 'X' en la “primera dimensión”
  2. si le pegan a la forma en que Perl mágicamente le agrada más :)

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

+0

Sí para 1. y Sí para 2, lamentablemente. Así que tendré que vivir con el hecho de que no existe una analogía en Python para Perl en este caso, un poco decepcionante. – Zhang18

+2

Imagina que estás saliendo, no lo sé, Nicole Kidman. Le dices que es un poco decepcionante que no tenga las curvas de, digamos, Monica Bellucci. ¿Puedes comenzar a comprender la falta de utilidad (y gracia) de tal afirmación? De todos modos, aparte de las parábolas, ISTM ha hecho su punto, y el mundo finalmente puede exhalar. – tzot

2

Dado que no sabemos de antemano si necesitamos un diccionario, o una lista, no se puede combinar la autovibración con las listas. A menos que, trabajando sobre la respuesta de Nosklo a partir de la pregunta vinculada, añada "características" de lista al diccionario subyacente. Básicamente asumiendo un orden de "clasificación" para las claves, y siempre usándolo con los métodos de la 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 de autovigilancia básica de generarse dinámicamente para las llaves dud. Sin embargo, también implementa algunos de los métodos listed here. Esto le permite actuar como una cosa cuasi-lista/dict.

Para el resto de las funciones 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, se realiza una suposición sobre el orden de los elementos que se retienen, es decir, que las cadenas se ordenan menos que los enteros, y que las claves están siempre en orden "ordenado".

También es compatible con la adición, como un ejemplo de these methods. Esto viene de mi propio caso de uso. Necesitaba agregar elementos de un diccionario de AutoVivified, pero si no existe, se crea y se devuelve un nuevo objeto AutoVivification.No tienen ningún "valor" número entero y por lo que no pueden hacer esto:

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

Eso contradice el objetivo, ya que no sé si alguna cosa va a estar ahí, pero yo quiero un defecto de todos modos. Así que le agregué los métodos __add__ y __radd__. Utilizan el length del diccionario subyacente como el valor integer, por lo que un objeto AV recién creado tiene un valor de cero para la suma. Si una clave tiene algo más que un objeto AV, obtendremos el método de adición de esa cosa, si se implementa.

1

Simplemente expandiendo la respuesta de Ignacio para presentar algunas opciones adicionales que le permiten solicitar explícitamente más comportamiento mágico de los diccionarios de Python. El mantenimiento del código escrito de esta manera sigue siendo dudoso, pero quería dejar en claro que se trata de "¿Es este tipo de estructura de datos mantenible?" (Tengo mis dudas) no "¿Se puede hacer que Python se comporte de esta manera?" (ciertamente puede).

Para apoyar niveles arbitrarios de anidación para el aspecto de espacio de nombres, todo lo que tiene que hacer es el nombre de la función (en lugar de utilizar lambda) y que sea auto-referencial:

>>> from collections import defaultdict 
>>> def autodict(): return defaultdict(autodict) 
... 
>>> a = autodict() 
>>> a[1][2][3] = [] 
>>> type(a[1]) 
<class 'collections.defaultdict'> 
>>> type(a[1][2]) 
<class 'collections.defaultdict'> 
>>> type(a[1][2][3]) 
<class 'list'> 
>>> 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 para que yace en el método setdefault, que en realidad ha vivido más 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] 

Todo collections.defaultdict realmente hace es hacer que el caso común donde siempre pasa el mismo segundo parámetro a dict.setdefault 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 collections.defaultdict no puede manejar.

Cuestiones relacionadas