2009-06-24 11 views
19

Estoy tratando de convertir los datos de un simple gráfico de objetos en un diccionario. No necesito información o métodos de tipo y no necesito volver a convertirlo en un objeto.Convertir recursivamente el gráfico de objetos de Python al diccionario

Encontré this question about creating a dictionary from an object's fields, pero no lo hace recursivamente.

Siendo relativamente nuevo en python, me preocupa que mi solución pueda ser fea, o antiponética, o que se rompa de alguna manera oscura, o simplemente vieja NIH.

Mi primer intento pareció funcionar hasta que lo intenté con listas y diccionarios, y me pareció más fácil simplemente comprobar si el objeto pasado tenía un diccionario interno, y si no, simplemente tratarlo como un valor (en lugar de hacerlo todo eso es comprobación de instancia). Mis intentos anteriores tampoco son recursivos en listas de objetos:

def todict(obj): 
    if hasattr(obj, "__iter__"): 
     return [todict(v) for v in obj] 
    elif hasattr(obj, "__dict__"): 
     return dict([(key, todict(value)) 
      for key, value in obj.__dict__.iteritems() 
      if not callable(value) and not key.startswith('_')]) 
    else: 
     return obj 

Esto parece funcionar mejor y no requiere excepciones, pero de nuevo todavía no estoy seguro de si hay casos que aquí no estoy al tanto de donde cae

Cualquier sugerencia sería muy apreciada.

+2

en Python no es tan malo para usar excepciones ya veces puede simplificar la codificación, una manera- Pythonic EAFP (más fácil pedir perdón que permiso) –

+0

caso especial podría ser cuando el objeto tiene __slots__, respuesta editada –

+1

punto tomado, pero la excepción es un poco de guerra santa y tiendo a preferir que nunca sean arrojados a menos que algo sea realmente excepcional, en lugar de un flujo de programa esperado. cada uno por su cuenta en ese caso :) – Shabbyrobe

Respuesta

27

Una amalgama de mi propio intento y pistas derivado de Anurag Uniyal y respuestas de Lennart Regebro funciona mejor para mí:

def todict(obj, classkey=None): 
    if isinstance(obj, dict): 
     data = {} 
     for (k, v) in obj.items(): 
      data[k] = todict(v, classkey) 
     return data 
    elif hasattr(obj, "_ast"): 
     return todict(obj._ast()) 
    elif hasattr(obj, "__iter__"): 
     return [todict(v, classkey) for v in obj] 
    elif hasattr(obj, "__dict__"): 
     data = dict([(key, todict(value, classkey)) 
      for key, value in obj.__dict__.iteritems() 
      if not callable(value) and not key.startswith('_')]) 
     if classkey is not None and hasattr(obj, "__class__"): 
      data[classkey] = obj.__class__.__name__ 
     return data 
    else: 
     return obj 
+0

muy bien hecho. única implementación que funciona como yo quería, hasta ahora. –

+0

¡solución elegante! – mvexel

+0

increíble, solo me devolvió horas de mi vida ... ¡gracias! – pixelphantom

5

No sé cuál es el propósito de verificar la existencia de la base o del objeto? también dict no contendrá ninguna callables a menos que tenga atributos que apuntan a tales callables, pero en ese caso no es esa parte del objeto?

así que en lugar de buscar varios tipos y valores, deje que todict convierta el objeto y si aumenta la excepción, utilice el valor original.

todict solo aumentará la excepción si obj no tiene dict p.

class A(object): 
    def __init__(self): 
     self.a1 = 1 

class B(object): 
    def __init__(self): 
     self.b1 = 1 
     self.b2 = 2 
     self.o1 = A() 

    def func1(self): 
     pass 

def todict(obj): 
    data = {} 
    for key, value in obj.__dict__.iteritems(): 
     try: 
      data[key] = todict(value) 
     except AttributeError: 
      data[key] = value 
    return data 

b = B() 
print todict(b) 

imprime { 'B1': 1, 'b2': 2, 'O1': { 'a1': 1}} puede haber algunos otros casos a considerar, pero puede ser un buen iniciar

casos especiales si un objeto utiliza ranuras, entonces no será capaz de obtener dict por ejemplo,

class A(object): 
    __slots__ = ["a1"] 
    def __init__(self): 
     self.a1 = 1 

solución para los casos de las ranuras puede ser el uso de dir() en lugar de directamente a través de la dict

+0

Gracias por la ayuda y la inspiración. Me di cuenta de que no maneja listas de objetos, así que actualicé mi versión para probar __iter__. Sin embargo, no estoy seguro de si es una buena idea. – Shabbyrobe

+0

parece que se volverá más complicado porque lo que sucede con un objeto que proporciona un iterador para iterar un atributo de lista que ya ha puesto en dict, puede ser que la solución general no sea posible. –

2

En Python hay muchas maneras de hacer que los objetos se comportan de forma ligeramente diferente, como metaclases y todo eso, y puede anular getattr y así tener atributos "mágicos" que no puede ver a través de dict, etc. En resumen, es poco probable que obtenga una imagen completa al 100% en el caso genérico con cualquier método que use .

Por lo tanto, la respuesta es: si le funciona en el caso de uso que tiene ahora, entonces el código es correcto. ;-)

Para hacer que el código un poco más genérico que podría hacer algo como esto:

import types 
def todict(obj): 
    # Functions, methods and None have no further info of interest. 
    if obj is None or isinstance(subobj, (types.FunctionType, types.MethodType)) 
     return obj 

    try: # If it's an iterable, return all the contents 
     return [todict(x) for x in iter(obj)] 
    except TypeError: 
     pass 

    try: # If it's a dictionary, recurse over it: 
     result = {} 
     for key in obj: 
      result[key] = todict(obj) 
     return result 
    except TypeError: 
     pass 

    # It's neither a list nor a dict, so it's a normal object. 
    # Get everything from dir and __dict__. That should be most things we can get hold of. 
    attrs = set(dir(obj)) 
    try: 
     attrs.update(obj.__dict__.keys()) 
    except AttributeError: 
     pass 

    result = {} 
    for attr in attrs: 
     result[attr] = todict(getattr(obj, attr, None)) 
    return result    

Algo por el estilo.Sin embargo, ese código no ha sido probado. Esto todavía no cubre el caso cuando anula getattr, y estoy seguro de que hay muchos más casos que no cubre y que pueden no ser cubiertos. :)

1

una manera lenta pero fácil de hacer esto es utilizar jsonpickle para convertir el objeto a una cadena JSON y luego json.loads para convertir de nuevo a un diccionario de Python:

dict = json.loads(jsonpickle.encode(obj, unpicklable=False))

1

que se dan cuenta de que esta respuesta es unos años demasiado tarde, pero pensé que podría valer la pena sha anillo ya que es una modificación de Python 3.3+ compatibles con la solución original por @Shabbyrobe que por lo general ha funcionado bien para mí:

import collections 
try: 
    # Python 2.7+ 
    basestring 
except NameError: 
    # Python 3.3+ 
    basestring = str 

def todict(obj): 
    """ 
    Recursively convert a Python object graph to sequences (lists) 
    and mappings (dicts) of primitives (bool, int, float, string, ...) 
    """ 
    if isinstance(obj, basestring): 
    return obj 
    elif isinstance(obj, dict): 
    return dict((key, todict(val)) for key, val in obj.items()) 
    elif isinstance(obj, collections.Iterable): 
    return [todict(val) for val in obj] 
    elif hasattr(obj, '__dict__'): 
    return todict(vars(obj)) 
    elif hasattr(obj, '__slots__'): 
    return todict(dict((name, getattr(obj, name)) for name in getattr(obj, '__slots__'))) 
    return obj 

Si usted no está interesado en los atributos que se puede llamar, por ejemplo, pueden ser despojados en el diccionario por comprensión:

elif isinstance(obj, dict): 
    return dict((key, todict(val)) for key, val in obj.items() if not callable(val)) 
0

Una pequeña actualización a la respuesta de Shabbyrobe para hacer que funcione para namedtuple s:

def obj2dict(obj, classkey=None): 
    if isinstance(obj, dict): 
     data = {} 
     for (k, v) in obj.items(): 
      data[k] = obj2dict(v, classkey) 
     return data 
    elif hasattr(obj, "_asdict"): 
     return obj2dict(obj._asdict()) 
    elif hasattr(obj, "_ast"): 
     return obj2dict(obj._ast()) 
    elif hasattr(obj, "__iter__"): 
     return [obj2dict(v, classkey) for v in obj] 
    elif hasattr(obj, "__dict__"): 
     data = dict([(key, obj2dict(value, classkey)) 
        for key, value in obj.__dict__.iteritems() 
        if not callable(value) and not key.startswith('_')]) 
     if classkey is not None and hasattr(obj, "__class__"): 
      data[classkey] = obj.__class__.__name__ 
     return data 
    else: 
     return obj 
1

Una línea de código a conv ert objeto JSON de forma recursiva

import json 
print(json.dumps(a, default=lambda o: getattr(o, '__dict__', str(o)))) 
Cuestiones relacionadas