2010-10-18 7 views
3

Je veux créer une structure polymorphe qui peut être créée à la volée avec un effort de frappe minimum et être très lisible. Par exemple:Dictionnaires imbriqués en Python, avec création implicite de conteneurs intermédiaires non existants?

a.b = 1 
a.c.d = 2 
a.c.e = 3 
a.f.g.a.b.c.d = cucu 
a.aaa = bau 

Je ne veux pas créer un récipient intermédiaire tel que:

a.c = subobject() 
a.c.d = 2 
a.c.e = 3 

Ma question est semblable à celui-ci:

What is the best way to implement nested dictionaries?

Mais je ne suis pas heureux avec la solution là parce que je pense qu'il y a un bug:
Les articles seront créés même quand vous ne voulez pas: supposer e vous voulez comparer 2 structures polymorphes: cela créera dans la 2ème structure n'importe quel attribut qui existe dans le premier et qui est juste vérifié dans l'autre. par exemple:

a = {1:2, 3: 4} 
b = {5:6} 

# now compare them: 

if b[1] == a[1] 
    # whoops, we just created b[1] = {} ! 

Je veux aussi obtenir la plus simple notation possible

a.b.c.d = 1 
    # neat 
a[b][c][d] = 1 
    # yuck 

J'ai essayé de tirer de la classe d'objet ... mais je ne pouvais pas éviter de laisser le même bug que ci-dessus où les attributs sont nés juste en essayant de les lire: simple dir() tenterait de créer des attributs tels que « méthodes » ... comme dans cet exemple, ce qui est évidemment cassé:

class KeyList(object): 
    def __setattr__(self, name, value): 
     print "__setattr__ Name:", name, "value:", value 
     object.__setattr__(self, name, value) 
    def __getattribute__(self, name): 
     print "__getattribute__ called for:", name 
     return object.__getattribute__(self, name) 
    def __getattr__(self, name): 
     print "__getattr__ Name:", name 
     try: 
      ret = object.__getattribute__(self, name) 
     except AttributeError: 
      print "__getattr__ not found, creating..." 
      object.__setattr__(self, name, KeyList()) 
      ret = object.__getattribute__(self, name) 
     return ret 

>>> cucu = KeyList() 
>>> dir(cucu) 
__getattribute__ called for: __dict__ 
__getattribute__ called for: __members__ 
__getattr__ Name: __members__ 
__getattr__ not found, creating... 
__getattribute__ called for: __methods__ 
__getattr__ Name: __methods__ 
__getattr__ not found, creating... 
__getattribute__ called for: __class__ 

Merci, vraiment!

p.s .: la meilleure solution que je trouve à ce jour est:

class KeyList(dict): 
    def keylset(self, path, value): 
     attr = self 
     path_elements = path.split('.') 
     for i in path_elements[:-1]: 
      try: 
       attr = attr[i] 
      except KeyError: 
       attr[i] = KeyList() 
       attr = attr[i] 
     attr[path_elements[-1]] = value 

# test 
>>> a = KeyList() 
>>> a.keylset("a.b.d.e", "ferfr") 
>>> a.keylset("a.b.d", {}) 
>>> a 
{'a': {'b': {'d': {}}}} 

# shallow copy 
>>> b = copy.copy(a) 
>>> b 
{'a': {'b': {'d': {}}}} 
>>> b.keylset("a.b.d", 3) 
>>> b 
{'a': {'b': {'d': 3}}} 
>>> a 
{'a': {'b': {'d': 3}}} 

# complete copy 
>>> a.keylset("a.b.d", 2) 
>>> a 
{'a': {'b': {'d': 2}}} 
>>> b 
{'a': {'b': {'d': 2}}} 
>>> b = copy.deepcopy(a) 
>>> b.keylset("a.b.d", 4) 
>>> b 
{'a': {'b': {'d': 4}}} 
>>> a 
{'a': {'b': {'d': 2}}} 
+0

Demandez-vous au sujet de 'collections.defaultdict'? –

+0

J'ai ajouté un exemple de classe dérivé de dict. C'est aussi proche que je pouvais obtenir ce que je voulais. – RumburaK

Répondre

1

Je pense au minimum que vous devez faire un chèque __getattr__ que le attrib demandé ne démarre pas et se terminent par __. Les attributs qui correspondent à cette description implémentent les API Python établies, donc vous ne devriez pas instancier ces attributs. Même ainsi, vous finirez par implémenter certaines attribtions API, comme par exemple next. Dans ce cas, vous obtiendrez une exception si vous passez l'objet à une fonction qui utilise le typage du dard pour voir s'il s'agit d'un itérateur.

Il serait vraiment préférable de créer une "liste blanche" de noms d'attributs valides, soit sous la forme d'un ensemble littéral, soit avec une formule simple: par ex. name.isalpha() and len(name) == 1 fonctionnerait pour les attributs d'une lettre que vous utilisez dans l'exemple. Pour une mise en œuvre plus réaliste, vous voudrez probablement définir un ensemble de noms appropriés au domaine dans lequel votre code fonctionne.

Je suppose que l'alternative est de s'assurer que vous ne créez pas dynamiquement l'un des divers attributs. les noms qui font partie d'un protocole, car next fait partie du protocole d'itération. Les méthodes de l'ABC dans le collections module comprennent une liste partielle, mais je ne sais pas où en trouver une pleine.

Vous devrez également savoir si l'objet a créé de tels nœuds enfants, afin que vous sachiez comment effectuer des comparaisons avec d'autres objets de ce type.

Si vous voulez des comparaisons pour éviter autovivification, vous devrez mettre en œuvre une méthode __cmp__ ou rich comparison methods, dans la classe qui vérifie comparés les __dict__ s des objets. J'ai l'impression que certaines complications ne me viennent pas à l'esprit, ce qui ne serait pas surprenant, car ce n'est pas vraiment ce que Python est censé faire. Marchez prudemment, et réfléchissez à la valeur de la complexité de cette approche.

+0

merci pour les conseils, mais vous devez être en mesure de définir le nom que vous voulez et instancier aucun des référencés :-) – RumburaK

+0

Oui, eh bien je pense que c'est l'alternative à une entreprise autrement risquée, où vous êtes potentiellement "implémentation" méthodes qui pourraient causer des interprétations incorrectes de la part de certaines fonctions python. Si vous voulez simplement l'utiliser comme données à plat, et ne jamais le passer à aucune fonction, juste filtrer '__name__' serait probablement assez bon. – intuited

1

Si vous cherchez quelque chose qui n'est pas aussi dynamique que votre message original, mais qui ressemble davantage à votre meilleure solution à ce jour, vous pourriez voir si la formencode de Ian Bicking répondrait à vos besoins. Le package lui-même est destiné pour les formulaires Web et la validation, mais quelques-unes des méthodes semblent assez proches de ce que vous cherchez.
Si rien d'autre, il pourrait servir d'exemple pour votre propre implémentation.

Un petit exemple:

>>> from formencode.variabledecode import variable_decode, variable_encode 
>>> 
>>> d={'a.b.c.d.e': 1} 
>>> variable_decode(d) 
{'a': {'b': {'c': {'d': {'e': 1}}}}} 
>>> 
>>> d['a.b.x'] = 3 
>>> variable_decode(d) 
{'a': {'b': {'c': {'d': {'e': 1}}, 'x': 3}}} 
>>> 
>>> d2 = variable_decode(d) 
>>> variable_encode(d2) == d 
True 
Questions connexes