2010-09-26 10 views
21

Sto costruendo del codice Python per leggere e manipolare dict profondamente nidificati (in definitiva per interagire con i servizi JSON, tuttavia sarebbe bello avere per altri scopi) I ' Sto cercando un modo per leggere facilmente/impostare/aggiornare i valori in profondità all'interno del dict, senza bisogno di molto codice.Python: accedere facilmente a dict nidificato (ottenere e impostare)

@see anche Python: Recursively access dict via attributes as well as index access? - La soluzione "DotDictify" di Curt Hagenlocher è piuttosto eloquente. Mi piace anche quello che Ben Alman presenta per JavaScript in http://benalman.com/projects/jquery-getobject-plugin/ Sarebbe bello combinare in qualche modo i due.

costruzione al largo di Curt Hagenlocher ed esempi di Ben Alman, sarebbe bello in Python per avere una capacità simile:

>>> my_obj = DotDictify() 
>>> my_obj.a.b.c = {'d':1, 'e':2} 
>>> print my_obj 
{'a': {'b': {'c': {'d': 1, 'e': 2}}}} 
>>> print my_obj.a.b.c.d 
1 
>>> print my_obj.a.b.c.x 
None 
>>> print my_obj.a.b.c.d.x 
None 
>>> print my_obj.a.b.c.d.x.y.z 
None 

Qualsiasi idea se questo è possibile, e se sì, come andare sulla modifica del DotDictify soluzione?

In alternativa, è possibile eseguire il metodo get per accettare una notazione punto (e un metodo set complementare aggiunto), tuttavia la notazione oggetto è più pulita.

>>> my_obj = DotDictify() 
>>> my_obj.set('a.b.c', {'d':1, 'e':2}) 
>>> print my_obj 
{'a': {'b': {'c': {'d': 1, 'e': 2}}}} 
>>> print my_obj.get('a.b.c.d') 
1 
>>> print my_obj.get('a.b.c.x') 
None 
>>> print my_obj.get('a.b.c.d.x') 
None 
>>> print my_obj.get('a.b.c.d.x.y.z') 
None 

Questo tipo di interazione sarebbe ottimo per avere a che fare con dts profondamente annidati. Qualcuno conosce un'altra strategia (o uno snippet/libreria di codice di esempio) da provare?

risposta

33

Albero Attributi

Il problema con il vostro prima specifica è che Python non può dire in __getitem__ se, a my_obj.a.b.c.d, vi verrà ora procederà più in basso un albero inesistente, nel qual caso ha bisogno di tornare un oggetto con un metodo __getitem__ in modo da non ottenere un valore AttributeError o se si desidera un valore, nel qual caso è necessario restituire None.

Io direi che in ogni caso hai sopra, dovresti aspettarti che lanci un KeyError invece di restituire None. Il motivo è che non si può sapere se None significa "nessuna chiave" o "qualcuno ha effettivamente memorizzato None in quella posizione". Per questo comportamento, tutto quello che dovete fare è prendere dotdictify, rimuovere , e sostituirlo con __getitem__:

def __getitem__(self, key): 
    return self[key] 

Perché ciò che si vuole veramente è un dict con __getattr__ e __setattr__.

Ci può essere un modo per rimuovere __getitem__ del tutto e dire qualcosa di simile __getattr__ = dict.__getitem__, ma penso che questo può essere eccesso di ottimizzazione, e sarà un problema se in seguito si decide che si desidera __getitem__ creare l'albero come va come dotdictify originariamente fa, nel qual caso si cambiarlo in:

def __getitem__(self, key): 
    if key not in self: 
     dict.__setitem__(self, key, dotdictify()) 
    return dict.__getitem__(self, key) 

non mi piace il affari in originale dotdictify.

percorso di supporto

La seconda specifica (override di get() e set()) è che un normale dict ha un get() che opera in modo diverso da ciò che si descrive e non ha nemmeno una set (anche se ha una setdefault() che è un'operazione inversa a get()). Le persone si aspettano che lo get prenda due parametri, il secondo è un valore predefinito se la chiave non viene trovata.

Se si desidera estendere __getitem__ e __setitem__ per gestire notazione chiave, è necessario modificare doctictify a:

class dotdictify(dict): 
    def __init__(self, value=None): 
     if value is None: 
      pass 
     elif isinstance(value, dict): 
      for key in value: 
       self.__setitem__(key, value[key]) 
     else: 
      raise TypeError, 'expected dict' 

    def __setitem__(self, key, value): 
     if '.' in key: 
      myKey, restOfKey = key.split('.', 1) 
      target = self.setdefault(myKey, dotdictify()) 
      if not isinstance(target, dotdictify): 
       raise KeyError, 'cannot set "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target)) 
      target[restOfKey] = value 
     else: 
      if isinstance(value, dict) and not isinstance(value, dotdictify): 
       value = dotdictify(value) 
      dict.__setitem__(self, key, value) 

    def __getitem__(self, key): 
     if '.' not in key: 
      return dict.__getitem__(self, key) 
     myKey, restOfKey = key.split('.', 1) 
     target = dict.__getitem__(self, myKey) 
     if not isinstance(target, dotdictify): 
      raise KeyError, 'cannot get "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target)) 
     return target[restOfKey] 

    def __contains__(self, key): 
     if '.' not in key: 
      return dict.__contains__(self, key) 
     myKey, restOfKey = key.split('.', 1) 
     target = dict.__getitem__(self, myKey) 
     if not isinstance(target, dotdictify): 
      return False 
     return restOfKey in target 

    def setdefault(self, key, default): 
     if key not in self: 
      self[key] = default 
     return self[key] 

    __setattr__ = __setitem__ 
    __getattr__ = __getitem__ 

Codice di prova:

>>> life = dotdictify({'bigBang': {'stars': {'planets': {}}}}) 
>>> life.bigBang.stars.planets 
{} 
>>> life.bigBang.stars.planets.earth = { 'singleCellLife' : {} } 
>>> life.bigBang.stars.planets 
{'earth': {'singleCellLife': {}}} 
>>> life['bigBang.stars.planets.mars.landers.vikings'] = 2 
>>> life.bigBang.stars.planets.mars.landers.vikings 
2 
>>> 'landers.vikings' in life.bigBang.stars.planets.mars 
True 
>>> life.get('bigBang.stars.planets.mars.landers.spirit', True) 
True 
>>> life.setdefault('bigBang.stars.planets.mars.landers.opportunity', True) 
True 
>>> 'landers.opportunity' in life.bigBang.stars.planets.mars 
True 
>>> life.bigBang.stars.planets.mars 
{'landers': {'opportunity': True, 'vikings': 2}} 
+0

Grazie mille, Mike. Ho aggiunto una funzione get che accetta la notazione dei punti (e un valore predefinito, come hai notato) Penso che questa nuova classe dotdictify renderà la vita molto più facile da gestire con dicts profondamente annidati. Grazie mille. – Hal

+0

Hai bisogno di una funzione 'get()'? Che cosa fa l'esistente 'get()' no? Il 'get()' che hai descritto nella domanda è equivalente a 'get (key, None)' che ottieni gratuitamente da 'dict'. –

+0

Quando ho usato la classe dotdictify "as-is" con l'installazione di Python 2.5 (Google App Engine SDK) la funzione get non stava gestendo le richieste di notazione dei punti per qualche motivo. Così ho scritto un wrapper veloce per la funzione get() per controllare la notazione dei punti e, in tal caso, passare a __getattr__ (restituendo il valore predefinito sull'eccezione), altrimenti passare a dict.get (self, key, default) – Hal

2

Avevo usato qualcosa di simile per costruire somithing simili Trie per un'applicazione. Spero possa essere d'aiuto.

class Trie: 
    """ 
    A Trie is like a dictionary in that it maps keys to values. 
    However, because of the way keys are stored, it allows 
    look up based on the longest prefix that matches. 

    """ 

    def __init__(self): 
     # Every node consists of a list with two position. In 
     # the first one,there is the value while on the second 
     # one a dictionary which leads to the rest of the nodes. 
     self.root = [0, {}] 


    def insert(self, key): 
     """ 
     Add the given value for the given key. 

     >>> a = Trie() 
     >>> a.insert('kalo') 
     >>> print(a) 
     [0, {'k': [1, {'a': [1, {'l': [1, {'o': [1, {}]}]}]}]}] 
     >>> a.insert('kalo') 
     >>> print(a) 
     [0, {'k': [2, {'a': [2, {'l': [2, {'o': [2, {}]}]}]}]}] 
     >>> b = Trie() 
     >>> b.insert('heh') 
     >>> b.insert('ha') 
     >>> print(b) 
     [0, {'h': [2, {'a': [1, {}], 'e': [1, {'h': [1, {}]}]}]}] 

     """ 

     # find the node to append the new value. 
     curr_node = self.root 
     for k in key: 
      curr_node = curr_node[1].setdefault(k, [0, {}]) 
      curr_node[0] += 1 


    def find(self, key): 
     """ 
     Return the value for the given key or None if key not 
     found. 

     >>> a = Trie() 
     >>> a.insert('ha') 
     >>> a.insert('ha') 
     >>> a.insert('he') 
     >>> a.insert('ho') 
     >>> print(a.find('h')) 
     4 
     >>> print(a.find('ha')) 
     2 
     >>> print(a.find('he')) 
     1 

     """ 

     curr_node = self.root 
     for k in key: 
      try: 
       curr_node = curr_node[1][k] 
      except KeyError: 
       return 0 
     return curr_node[0] 

    def __str__(self): 
     return str(self.root) 

    def __getitem__(self, key): 
     curr_node = self.root 
     for k in key: 
      try: 
       curr_node = curr_node[1][k] 
      except KeyError: 
       yield None 
     for k in curr_node[1]: 
      yield k, curr_node[1][k][0] 

if __name__ == '__main__': 
    a = Trie() 
    a.insert('kalo') 
    a.insert('kala') 
    a.insert('kal') 
    a.insert('kata') 
    print(a.find('kala')) 
    for b in a['ka']: 
     print(b) 
    print(a) 
1

ai colleghi Googler: abbiamo ora addict:

pip install addict 

e

mapping.a.b.c.d.e = 2 
mapping 
{'a': {'b': {'c': {'d': {'e': 2}}}}} 

L'ho usato estesamente.

Per lavorare con percorsi tratteggiate, ho trovato dotted:

obj = DottedDict({'hello': {'world': {'wide': 'web'}}}) 
obj['hello.world.wide'] == 'web' # true 
Problemi correlati