Esto es un problema interesante. Parece que Greg tiene un good solution. Pero eso no es divertido;)
jsbueno como very elegant solution pero eso solo se aplica a las cadenas (como lo solicitó).
El truco de un diccionario auto referencial "general" es utilizar un objeto sustituto. Se tarda unos pocos (subestimación) líneas de código de lograr, pero el uso es en lo que quiere:
S = SurrogateDict(AdditionSurrogateDictEntry)
d = S.resolve({'user': 'gnucom',
'home': '/home/' + S['user'],
'config': [S['home'] + '/.emacs', S['home'] + '/.bashrc']})
El código para que esto suceda no es tan corto.Vive en tres clases:
import abc
class SurrogateDictEntry(object):
__metaclass__ = abc.ABCMeta
def __init__(self, key):
"""record the key on the real dictionary that this will resolve to a
value for
"""
self.key = key
def resolve(self, d):
""" return the actual value"""
if hasattr(self, 'op'):
# any operation done on self will store it's name in self.op.
# if this is set, resolve it by calling the appropriate method
# now that we can get self.value out of d
self.value = d[self.key]
return getattr(self, self.op + 'resolve__')()
else:
return d[self.key]
@staticmethod
def make_op(opname):
"""A convience class. This will be the form of all op hooks for subclasses
The actual logic for the op is in __op__resolve__ (e.g. __add__resolve__)
"""
def op(self, other):
self.stored_value = other
self.op = opname
return self
op.__name__ = opname
return op
A continuación viene la clase concreta. Suficientemente simple.
class AdditionSurrogateDictEntry(SurrogateDictEntry):
__add__ = SurrogateDictEntry.make_op('__add__')
__radd__ = SurrogateDictEntry.make_op('__radd__')
def __add__resolve__(self):
return self.value + self.stored_value
def __radd__resolve__(self):
return self.stored_value + self.value
Aquí está la clase final
class SurrogateDict(object):
def __init__(self, EntryClass):
self.EntryClass = EntryClass
def __getitem__(self, key):
"""record the key and return"""
return self.EntryClass(key)
@staticmethod
def resolve(d):
"""I eat generators resolve self references"""
stack = [d]
while stack:
cur = stack.pop()
# This just tries to set it to an appropriate iterable
it = xrange(len(cur)) if not hasattr(cur, 'keys') else cur.keys()
for key in it:
# sorry for being a duche. Just register your class with
# SurrogateDictEntry and you can pass whatever.
while isinstance(cur[key], SurrogateDictEntry):
cur[key] = cur[key].resolve(d)
# I'm just going to check for iter but you can add other
# checks here for items that we should loop over.
if hasattr(cur[key], '__iter__'):
stack.append(cur[key])
return d
En respuesta a la pregunta de por qué gnucoms puse el nombre de las clases de la forma en que lo hice.
La palabra sustituta generalmente se asocia con sustituirse por otra cosa, por lo que parece apropiada porque eso es lo que hace la clase SurrogateDict
: una instancia reemplaza las referencias "propias" en un literal de diccionario. Habiendo dicho eso, (aparte de ser simplemente estúpido), nombrar es probablemente una de las cosas más difíciles para mí sobre la codificación. Si usted (o cualquier otra persona) puede sugerir un nombre mejor, soy todo oídos.
Proporcionaré una breve explicación. A lo largo de S
se refiere a una instancia de SurrogateDict y d
es el diccionario real.
Una referencia S[key]
desencadena S.__getitem__
y SurrogateDictEntry(key)
para ser colocado en el d
.
Cuando se construye S[key] = SurrogateDictEntry(key)
, almacena key
. Este será el key
en d
para el valor que esta entrada de SurrogateDictEntry
está actuando como un sustituto para.
Después de que se devuelve S[key]
, se ingresa en el d
, o se le realizan algunas operaciones. Si se realiza una operación en él, desencadena el método relativo __op__
que simplemente almacena el valor en el que se realiza la operación y el nombre de la operación y luego se devuelve a sí mismo. Realmente no podemos resolver la operación porque d
no se ha construido aún.
Después de construir d
, se pasa al S.resolve
. Este método recorre d
buscando instancias de SurrogateDictEntry
y reemplazándolos con el resultado de llamar al método resolve
en la instancia.
El método SurrogateDictEntry.resolve
recibe ahora construido d
como un argumento y puede utilizar el valor de key
que almacena en el momento de la construcción para obtener el valor que está actuando como un sustituto para. Si se realizó una operación en él después de la creación, el atributo op
se habrá establecido con el nombre de la operación que se realizó. Si la clase tiene un método __op__
, entonces tiene un método __op__resolve__
con la lógica real que normalmente estaría en el método __op__
. Así que ahora tenemos la lógica (auto op__resolve) y todos los valores necesarios (self.value, self.stored_value) para obtener finalmente el valor real de d[key]
. Entonces devolvemos lo que paso 4 lugares en el diccionario.
finalmente el método SurrogateDict.resolve
devuelve d
con todas las referencias resueltas.
That'a a rough sketch. Si tiene más preguntas, no dude en preguntar.
¿Podría hacer que la expresión sea floja para arreglar esto? – sholsapp
¿Desea que el valor de 'dictionary ['b']' sea estático o dinámico? ¿Cómo se vería el 'diccionario' después de' dictionary ['a'] = '999''? –
@Will: estaba a punto de editar mi publicación para responder a esta pregunta. Quiero que sea estático, y me doy cuenta de que la respuesta de g.d.d.c sería por dinámica. :/ – sholsapp