Árbol de atributos
El problema con su primera especificación es que Python no puede decir en __getitem__
si, al my_obj.a.b.c.d
, lo próximo se procederá más abajo en un árbol que no existe, en cuyo caso se necesita para volver un objeto con un método __getitem__
por lo que no obtendrá un AttributeError
arrojado sobre usted, o si desea un valor, en cuyo caso debe devolver None
.
Yo diría que en todos los casos que tenga arriba, debe esperar arrojar un KeyError
en lugar de devolver None
. El motivo es que no puede decir si None
significa "sin clave" o "alguien actualmente almacena None
en esa ubicación". Por este comportamiento, todo lo que tiene que hacer es tomar dotdictify
, eliminar , y reemplazar con __getitem__
:
def __getitem__(self, key):
return self[key]
Porque lo que realmente quiere es un dict
con __getattr__
y __setattr__
.
Puede haber una manera de eliminar __getitem__
por completo y decir algo como __getattr__ = dict.__getitem__
, pero creo que esto puede ser sobre-optimización, y será un problema si más adelante decide que quiere __getitem__
para crear el árbol, ya que va como dotdictify
originalmente hace, en cuyo caso debe cambiarlo a:
def __getitem__(self, key):
if key not in self:
dict.__setitem__(self, key, dotdictify())
return dict.__getitem__(self, key)
no me gusta el negocio en el original dotdictify
.
compatibilidad con rutas
La segunda especificación (anulación get()
y set()
) es que una normal de dict
tiene un get()
que opera de manera diferente de lo que usted describe, y ni siquiera tiene un set
(aunque tiene un setdefault()
que es una operación inversa al get()
). La gente espera que get
tome dos parámetros, el segundo es un valor predeterminado si no se encuentra la clave.
Si desea ampliar __getitem__
y __setitem__
para manejar la notación de puntos-clave, tendrá que modificar 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__
Código de ensayo:
>>> 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}}
Muchas gracias, Mike. Agregué una función get que acepta la notación de punto (y un valor predeterminado, como lo anotaron). Creo que esta nueva clase dotdictify hará que la vida sea mucho más fácil al tratar con dicts profundamente anidados. Muchas gracias. – Hal
¿Necesita una función 'get()'? ¿Qué hace que el 'get()' existente no lo haga? El 'get()' que describiste en la pregunta es equivalente al 'get (key, None)' que obtienes gratis de 'dict'. –
Cuando utilicé la clase dotdictify "tal cual" con mi instalación de Python 2.5 (Google App Engine SDK), la función get no manejó las solicitudes de notación de puntos por algún motivo. Así que escribí un contenedor rápido para la función get() para verificar la notación de puntos y, de ser así, pasé a __getattr__ (devolviendo el valor predeterminado en excepción), de lo contrario pasaría a dict.get (self, key, default) – Hal