2010-09-26 9 views
21

Ich baue einig Python-Code tief verschachtelte dicts zu lesen und zu manipulieren (letztlich mit JSON Diensten für die Interaktion, aber es wäre toll, für andere Zwecke zu haben) I‘ Ich suche nach einer Möglichkeit, Werte innerhalb des Diktats einfach zu lesen/setzen/zu aktualisieren, ohne viel Code zu benötigen.Python: Leichte Zugang zu tief verschachtelten dict (get und set)

@see auch Python: Recursively access dict via attributes as well as index access? - Curt Hagenlocher Die "DotDictify" Lösung ist ziemlich eloquent. Ich mag auch, was Ben Alman für JavaScript in http://benalman.com/projects/jquery-getobject-plugin/ präsentiert Es wäre toll, die beiden irgendwie zu kombinieren.

Gebäude weg von Curt Hagenlocher und Ben Alman der Beispiele, wäre es toll in Python sein, eine Fähigkeit zu haben, wie:

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

Jede Idee, wenn dies möglich ist, und wenn ja, wie die Informationen zum Ändern gehen DotDictify Lösung?

Alternativ könnte die get-Methode hergestellt werden, um eine Punktnotation zu akzeptieren (und eine komplementäre Set-Methode hinzugefügt) jedoch das Objekt Notation sicher sauberer ist.

Diese Art der Interaktion wäre großartig für den Umgang mit tief verschachtelten Diktaten. Kennt jemand eine andere Strategie (oder Beispielcode-Ausschnitt/Bibliothek), um es zu versuchen?

Antwort

33

Attribut Baum

das Problem bei dem ersten Spezifikation ist, dass Python nicht in __getitem__ sagen kann, wenn bei my_obj.a.b.c.d, werden Sie als nächstes weiter unten einen nicht vorhandenen Baum gehen, wobei in diesem Fall muss er zurück ein Objekt mit einer __getitem__ Methode, so dass Sie keine AttributeError auf Sie geworfen werden, oder wenn Sie einen Wert möchten, in diesem Fall muss None zurückgegeben werden.

Ich würde argumentieren, dass in jedem Fall Sie oben haben, sollten Sie erwarten, dass es eine KeyError anstelle None zurückwerfen wird. Der Grund dafür ist, dass Sie nicht wissen können, ob None "kein Schlüssel" bedeutet oder "jemand tatsächlich None an diesem Ort gespeichert hat". Für dieses Verhalten, alles, was Sie tun müssen, ist dotdictify nehmen, entfernen Sie und ersetzen __getitem__ mit:

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

Weil das, was Sie wirklich wollen, eine dict mit __getattr__ und __setattr__ ist.

Es kann ein Weg sein, __getitem__ vollständig zu entfernen und etwas zu sagen wie __getattr__ = dict.__getitem__, aber ich denke, das Überoptimierung sein kann, und wird ein Problem sein, wenn Sie später Sie __getitem__ den Baum erstellen wollen entscheiden, wie es wie dotdictify geht ursprünglich der Fall ist, in dem Fall, dass Sie es ändern würde:

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

ich mag nicht in der ursprünglichen dotdictify die Geschäft.

Pfad Unterstützung

Die zweite Spezifikation (Überschreibung get() und set()) ist, dass ein normaler dict eine get() hat, die anders funktioniert, was Sie beschreiben und haben nicht einmal ein set (obwohl es eine setdefault() hat, die eine inverse Operation zu get()). Die Benutzer erwarten, dass get zwei Parameter verwendet, der zweite ist ein Standardwert, wenn der Schlüssel nicht gefunden wird.

Wenn Sie __getitem__ und __setitem__ erweitern möchten gepunkteten schlüssel Notation zu handhaben, müssen Sie doctictify ändern:

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__ 

Prüfregeln:

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

Vielen Dank, Mike. Ich habe eine get-Funktion hinzugefügt, die Punktnotation akzeptiert (und einen Standardwert, wie Sie notiert haben). Ich denke, dass diese neue dotdictify-Klasse das Leben mit tief verschachtelten Diktaten wesentlich erleichtern wird. Vielen Dank. – Hal

+0

Brauchen Sie eine 'get()' Funktion? Was macht das, dass das vorhandene 'get()' nicht funktioniert? Das 'get()', das Sie in der Frage beschrieben haben, entspricht dem 'get (key, None)', das Sie kostenlos von 'dict' erhalten. –

+0

Als ich die dotdictify-Klasse "wie besehen" mit meiner Python 2.5-Installation (Google App Engine SDK) verwendete, behandelte die get-Funktion aus irgendeinem Grund die Punktnotationsanforderungen nicht. Also schrieb ich einen schnellen Wrapper für die get() - Funktion, um nach Punktnotation zu suchen, und falls ja, pass auf __getattr__ (gibt die Standard-Ausnahme zurück), ansonsten gehe zu dict.get (self, key, default) – Hal

2

Ich hatte etwas ähnliches verwendet, um Trie für eine Anwendung somithing ähnlich zu bauen. Ich hoffe, es hilft.

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

Um Kerl Googler: wir Jetzt haben addict:

pip install addict 

und

Ich habe es ausgiebig verwendet.

mit gepunkteten Pfaden zu arbeiten, fand ich dotted:

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