Diccionario de Python de los campos de un objeto

340

¿Sabes si hay una función incorporada para construir un diccionario a partir de un objeto arbitrario? Me gustaría hacer algo como esto:

>>> class Foo:
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> props(f)
{ 'bar' : 'hello', 'baz' : 'world' }

NOTA: No debe incluir métodos. Solo campos.

Julio Cesar
fuente

Respuestas:

420

Tenga en cuenta que la mejor práctica en Python 2.7 es usar clases de estilo nuevo (no necesarias con Python 3), es decir

class Foo(object):
   ...

Además, hay una diferencia entre un 'objeto' y una 'clase'. Para construir un diccionario a partir de un objeto arbitrario , es suficiente usarlo __dict__. Por lo general, declarará sus métodos a nivel de clase y sus atributos a nivel de instancia, por lo que __dict__debería estar bien. Por ejemplo:

>>> class A(object):
...   def __init__(self):
...     self.b = 1
...     self.c = 2
...   def do_nothing(self):
...     pass
...
>>> a = A()
>>> a.__dict__
{'c': 2, 'b': 1}

Un mejor enfoque (sugerido por Robert en los comentarios) es la varsfunción incorporada :

>>> vars(a)
{'c': 2, 'b': 1}

Alternativamente, dependiendo de lo que quieras hacer, puede ser bueno heredar de dict. Entonces su clase ya es un diccionario, y si lo desea, puede anular getattry / o setattrllamar y establecer el dict. Por ejemplo:

class Foo(dict):
    def __init__(self):
        pass
    def __getattr__(self, attr):
        return self[attr]

    # etc...
usuario6868
fuente
3
¿Qué sucede si uno de los atributos de A tiene un captador personalizado? (una función con un decorador @property)? ¿Sigue apareciendo en ____dict____? ¿Cuál será su valor?
zakdances
11
__dict__no funcionará si el objeto está usando ranuras (o definido en un módulo C).
Antimonio
1
¿Existe un equivalente de este método para los objetos de clase? IE En lugar de usar f = Foo () y luego hacer f .__ dict__, ¿directamente Foo .__ dict__?
chiffa
49
Lo siento, voy a llegar tan tarde, pero ¿no debería vars(a)hacer esto? Para mí es preferible invocar __dict__directamente.
robert
2
para el segundo ejemplo, sería mejor hacerlo __getattr__ = dict.__getitem__para replicar exactamente el comportamiento, entonces también querría __setattr__ = dict.__setitem__y __delattr__ = dict.__delitem__para completar.
Tadhg McDonald-Jensen
140

En lugar de x.__dict__, en realidad es más pitónico de usar vars(x).

Berislav Lopac
fuente
3
Convenido. Tenga en cuenta que también puede convertir a la inversa (dict-> class) escribiendo MyClass(**my_dict), suponiendo que haya definido un constructor con parámetros que reflejen los atributos de la clase. No es necesario acceder a atributos privados o anular dict.
tvt173
2
¿Puedes explicar por qué es más Pythonic?
Hugh W
1
En primer lugar, Python generalmente evita directamente los elementos de llamada de dunder, y casi siempre hay un método u función (u operador) para acceder indirectamente. En general, los atributos y métodos dunder son un detalle de implementación, y el uso de la función "wrapper" le permite separar los dos. En segundo lugar, de esta manera puede anular la varsfunción e introducir funcionalidad adicional sin cambiar el objeto en sí.
Berislav Lopac
1
Sin embargo, todavía falla si su clase usa __slots__.
cz
Eso es correcto, y siempre sentí que sería una buena dirección extender vars, es decir, devolver un equivalente de las __dict__clases "ranuradas". Por ahora, se puede emular agregando una __dict__propiedad que devuelve {x: getattr(self, x) for x in self.__slots__}(aunque no estoy seguro si eso afecta el rendimiento o el comportamiento de alguna manera).
Berislav Lopac
59

El dirbuiltin le dará todos los atributos del objeto, incluidos métodos especiales como __str__, __dict__y un montón de otros que probablemente no desee. Pero puedes hacer algo como:

>>> class Foo(object):
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> [name for name in dir(f) if not name.startswith('__')]
[ 'bar', 'baz' ]
>>> dict((name, getattr(f, name)) for name in dir(f) if not name.startswith('__')) 
{ 'bar': 'hello', 'baz': 'world' }

Entonces, puede extender esto para devolver solo atributos de datos y no métodos, definiendo su propsfunción de esta manera:

import inspect

def props(obj):
    pr = {}
    for name in dir(obj):
        value = getattr(obj, name)
        if not name.startswith('__') and not inspect.ismethod(value):
            pr[name] = value
    return pr
dF.
fuente
1
Este código incluye métodos. ¿Hay alguna manera de excluir métodos? Solo necesito los campos del objeto. Gracias
Julio César
ismethodno atrapa funciones. Ejemplo: inspect.ismethod(str.upper). inspect.isfunctionSin embargo, no es mucho más útil. No estoy seguro de cómo abordar esto de inmediato.
Ehtesh Choudhury
Hice algunos ajustes para volver a aparecer e ignorar todos los errores a profundidad aquí, ¡gracias! gist.github.com/thorsummoner/bf0142fd24974a0ced778768a33a3069
ThorSummoner
26

Me decidí con una combinación de ambas respuestas:

dict((key, value) for key, value in f.__dict__.iteritems() 
    if not callable(value) and not key.startswith('__'))
Julio Cesar
fuente
Eso también funciona, pero tenga en cuenta que solo le dará los atributos establecidos en la instancia, no en la clase (como la clase Foo en su ejemplo) ...
dF.
Entonces, jcarrascal, es mejor que envuelva el código anterior en una función como props (), luego puede llamar a props (f) o props (Foo). Tenga en cuenta que casi siempre es mejor escribir una función, en lugar de escribir el código 'en línea'.
quamrana
Bueno, por cierto, esto es para python2.7, para python3 relpace iteritems () con simplemente elementos ().
Morten
¿Y de qué staticmethod? No es callable.
Alex
16

Pensé que me tomaría un tiempo mostrarle cómo puede traducir un objeto para dictarlo dict(obj).

class A(object):
    d = '4'
    e = '5'
    f = '6'

    def __init__(self):
        self.a = '1'
        self.b = '2'
        self.c = '3'

    def __iter__(self):
        # first start by grabbing the Class items
        iters = dict((x,y) for x,y in A.__dict__.items() if x[:2] != '__')

        # then update the class items with the instance items
        iters.update(self.__dict__)

        # now 'yield' through the items
        for x,y in iters.items():
            yield x,y

a = A()
print(dict(a)) 
# prints "{'a': '1', 'c': '3', 'b': '2', 'e': '5', 'd': '4', 'f': '6'}"

La sección clave de este código es la __iter__función.

Como explican los comentarios, lo primero que hacemos es tomar los elementos de la Clase y evitar cualquier cosa que comience con '__'.

Una vez que haya creado eso dict, puede usar la updatefunción dict y pasar la instancia __dict__.

Estos le darán un diccionario completo de clase + instancia de miembros. Ahora todo lo que queda es iterar sobre ellos y obtener los rendimientos.

Además, si planea usar esto mucho, puede crear un @iterabledecorador de clase.

def iterable(cls):
    def iterfn(self):
        iters = dict((x,y) for x,y in cls.__dict__.items() if x[:2] != '__')
        iters.update(self.__dict__)

        for x,y in iters.items():
            yield x,y

    cls.__iter__ = iterfn
    return cls

@iterable
class B(object):
    d = 'd'
    e = 'e'
    f = 'f'

    def __init__(self):
        self.a = 'a'
        self.b = 'b'
        self.c = 'c'

b = B()
print(dict(b))
Seaux
fuente
Esto capturará también todos los métodos, pero solo necesitamos campos de clase + instancia. Tal vez dict((x, y) for x, y in KpiRow.__dict__.items() if x[:2] != '__' and not callable(y))lo resuelva? Pero todavía podría haber staticmétodos :(
Alex
15

Para construir un diccionario a partir de un objeto arbitrario , es suficiente usarlo __dict__.

Esto pierde atributos que el objeto hereda de su clase. Por ejemplo,

class c(object):
    x = 3
a = c()

hasattr (a, 'x') es verdadero, pero 'x' no aparece en un .__ dict__

sangría
fuente
En este caso, ¿cuál es la solución? Dado vars()que no funciona
debería
@should_be_working dires la solución en este caso. Vea la otra respuesta sobre eso.
Albert
8

Respuesta tardía pero siempre completa y en beneficio de los googlers:

def props(x):
    return dict((key, getattr(x, key)) for key in dir(x) if key not in dir(x.__class__))

Esto no mostrará los métodos definidos en la clase, pero aún mostrará campos que incluyen los asignados a lambdas o aquellos que comienzan con un doble guión bajo.

Score_Under
fuente
6

Creo que la forma más fácil es crear un atributo getitem para la clase. Si necesita escribir en el objeto, puede crear una costumbre setattr . Aquí hay un ejemplo para getitem :

class A(object):
    def __init__(self):
        self.b = 1
        self.c = 2
    def __getitem__(self, item):
        return self.__dict__[item]

# Usage: 
a = A()
a.__getitem__('b')  # Outputs 1
a.__dict__  # Outputs {'c': 2, 'b': 1}
vars(a)  # Outputs {'c': 2, 'b': 1}

dict genera los atributos de los objetos en un diccionario y el objeto del diccionario se puede usar para obtener el elemento que necesita.

radtek
fuente
Después de esta respuesta aún no está claro cómo obtener un diccionario de un objeto. No propiedades, pero todo el diccionario;)
maxkoryukov
6

Una desventaja de usar __dict__es que es poco profunda; no convertirá ninguna subclase a diccionarios.

Si está utilizando Python3.5 o superior, puede usar jsons:

>>> import jsons
>>> jsons.dump(f)
{'bar': 'hello', 'baz': 'world'}
RH
fuente
3

Si desea enumerar parte de sus atributos, anule __dict__:

def __dict__(self):
    d = {
    'attr_1' : self.attr_1,
    ...
    }
    return d

# Call __dict__
d = instance.__dict__()

Esto ayuda mucho si instanceobtiene algunos datos de bloque grandes y desea enviar da Redis como cola de mensajes.

coanor
fuente
__dict__es un atributo, no un método, por lo que este ejemplo cambia la interfaz (es decir, debe llamarlo como invocable), por lo que no lo anula.
Berislav Lopac
0

PITÓN 3:

class DateTimeDecoder(json.JSONDecoder):

   def __init__(self, *args, **kargs):
        JSONDecoder.__init__(self, object_hook=self.dict_to_object,
                         *args, **kargs)

   def dict_to_object(self, d):
       if '__type__' not in d:
          return d

       type = d.pop('__type__')
       try:
          dateobj = datetime(**d)
          return dateobj
       except:
          d['__type__'] = type
          return d

def json_default_format(value):
    try:
        if isinstance(value, datetime):
            return {
                '__type__': 'datetime',
                'year': value.year,
                'month': value.month,
                'day': value.day,
                'hour': value.hour,
                'minute': value.minute,
                'second': value.second,
                'microsecond': value.microsecond,
            }
        if isinstance(value, decimal.Decimal):
            return float(value)
        if isinstance(value, Enum):
            return value.name
        else:
            return vars(value)
    except Exception as e:
        raise ValueError

Ahora puede usar el código anterior dentro de su propia clase:

class Foo():
  def toJSON(self):
        return json.loads(
            json.dumps(self, sort_keys=True, indent=4, separators=(',', ': '), default=json_default_format), cls=DateTimeDecoder)


Foo().toJSON() 
spattanaik75
fuente
0

vars() es genial, pero no funciona para objetos anidados de objetos

Convierte objetos anidados de objetos a dict:

def to_dict(self):
    return json.loads(json.dumps(self, default=lambda o: o.__dict__))
Ricky Levi
fuente