Iterar sobre los atributos del objeto en Python

162

Tengo un objeto python con varios atributos y métodos. Quiero iterar sobre los atributos del objeto.

class my_python_obj(object):
    attr1='a'
    attr2='b'
    attr3='c'

    def method1(self, etc, etc):
        #Statements

Quiero generar un diccionario que contenga todos los atributos de los objetos y sus valores actuales, pero quiero hacerlo de forma dinámica (así que si luego agrego otro atributo, no tengo que recordar actualizar mi función también).

En php, las variables se pueden usar como claves, pero los objetos en python no se pueden usar y si uso la notación de punto para esto, crea un nuevo atributo con el nombre de mi var, que no es mi intención.

Solo para aclarar las cosas:

def to_dict(self):
    '''this is what I already have'''
    d={}
    d["attr1"]= self.attr1
    d["attr2"]= self.attr2
    d["attr3"]= self.attr3
    return d

·

def to_dict(self):
    '''this is what I want to do'''
    d={}
    for v in my_python_obj.attributes:
        d[v] = self.v
    return d

Actualización: con atributos me refiero solo a las variables de este objeto, no a los métodos.

Pablo Mescher
fuente
3
stackoverflow.com/questions/1251692/… Podría ser de alguna ayuda.
sean
@sean En particular, stackoverflow.com/a/1251789/4203 es el camino a seguir; usaría el predicateargumento opcional para pasar un invocable que filtraría (¿enlazado?) funciones (que elimina los métodos).
Hank Gay
Per Sean, ¿esto responde a tu pregunta? ¿Cómo enumerar las propiedades de un objeto en Python?
Davis Herring

Respuestas:

227

Asumiendo que tienes una clase como

>>> class Cls(object):
...     foo = 1
...     bar = 'hello'
...     def func(self):
...         return 'call me'
...
>>> obj = Cls()

invocar direl objeto le devuelve todos los atributos de ese objeto, incluidos los atributos especiales de python. Aunque algunos atributos de objeto son invocables, como los métodos.

>>> dir(obj)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo', 'func']

Siempre puede filtrar los métodos especiales utilizando una lista de comprensión.

>>> [a for a in dir(obj) if not a.startswith('__')]
['bar', 'foo', 'func']

o si prefieres mapa / filtros.

>>> filter(lambda a: not a.startswith('__'), dir(obj))
['bar', 'foo', 'func']

Si desea filtrar los métodos, puede usar el incorporado callablecomo un cheque.

>>> [a for a in dir(obj) if not a.startswith('__') and not callable(getattr(obj, a))]
['bar', 'foo']

También puede inspeccionar la diferencia entre su clase y su objeto de instancia utilizando.

>>> set(dir(Cls)) - set(dir(object))
set(['__module__', 'bar', 'func', '__dict__', 'foo', '__weakref__'])
Meitham
fuente
1
Esta es una mala idea, ni siquiera lo sugeriría, pero si vas a hacerlo, al menos proporciona la advertencia.
Julián
2
@Meitham @Pablo Lo que está mal es principalmente que no deberías necesitar hacer esto. Los atributos que le interesan deben ser inclusivos, no exclusivos . Como ya has visto, estás incluyendo métodos, y cualquier intento de excluirlos será defectuoso, porque involucrarán cosas desagradables como callableo types.MethodType. ¿Qué sucede si agrego un atributo llamado "_foo" que almacena algún resultado intermedio o estructura de datos interna? ¿Qué sucede si simplemente agrego un atributo a la clase name, inadvertidamente, y ahora, de repente, se incluye?
Julián
3
@Julian, el código anterior elegirá ambos _fooy nameporque ahora son atributos del objeto. Si no desea incluirlos, puede excluirlos en la condición de comprensión de la lista. El código anterior es un idioma común de Python y una forma popular de introspección de objetos de Python.
Meitham
30
En realidad, obj .__ dict__ es (probablemente) mejor para este propósito.
Dave
55
@Julian dir()solo "miente" cuando pasa metaclases (un caso extremo) o clases con atributos decorados por @DynamicClassAttribute(otro caso extremo extremo). En ambos casos, llamar al dirs()contenedor inspect.getmembers()resuelve esto. Sin embargo, para los objetos estándar, el enfoque de comprensión de la lista de esta solución que filtra los no callables es absolutamente suficiente. Que algunos Pythonistas lo etiqueten como una "mala idea" me desconcierta, pero ... ¿a cada uno lo suyo, supongo?
Cecil Curry
57

en general ponga un __iter__método en su clase e itere a través de los atributos del objeto o ponga esta clase mixin en su clase.

class IterMixin(object):
    def __iter__(self):
        for attr, value in self.__dict__.iteritems():
            yield attr, value

Tu clase:

>>> class YourClass(IterMixin): pass
...
>>> yc = YourClass()
>>> yc.one = range(15)
>>> yc.two = 'test'
>>> dict(yc)
{'one': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 'two': 'test'}
SmartElectron
fuente
esto solo funciona cuando oney twose define después yc = YourClass(). La pregunta se refiere a recorrer los atributos existentes en YourClass(). Además, heredar la IterMixinclase puede no estar siempre disponible como solución.
Xiao
44
vars(yc)da el mismo resultado sin tener que heredar de otra clase.
Nathan
1
Mi comentario anterior no es del todo correcto; vars(yc)es casi lo mismo, pero el diccionario que le devuelve es propio de la instancia __dict__, por lo que modificarlo se reflejará en la instancia. Esto puede generar problemas si no tiene cuidado, por lo que el método anterior a veces podría ser mejor (aunque copiar el diccionario probablemente sea más fácil). Además, tenga en cuenta que si bien el diccionario devuelto anteriormente no es de la instancia __dict__, los valores son los mismos, por lo que la modificación de cualquier valor mutable se reflejará en los atributos de la instancia.
Nathan
25

Como ya se mencionó en algunas de las respuestas / comentarios, los objetos de Python ya almacenan un diccionario de sus atributos (los métodos no están incluidos). Se puede acceder a esto como __dict__, pero la mejor manera es usarlo vars(sin embargo, la salida es la misma). ¡Tenga en cuenta que la modificación de este diccionario modificará los atributos en la instancia! Esto puede ser útil, pero también significa que debe tener cuidado con la forma en que usa este diccionario. Aquí hay un ejemplo rápido:

class A():
    def __init__(self, x=3, y=2, z=5):
        self.x = x
        self._y = y
        self.__z__ = z

    def f(self):
        pass

a = A()
print(vars(a))
# {'x': 3, '_y': 2, '__z__': 5}
# all of the attributes of `a` but no methods!

# note how the dictionary is always up-to-date
a.x = 10
print(vars(a))
# {'x': 10, '_y': 2, '__z__': 5}

# modifying the dictionary modifies the instance attribute
vars(a)["_y"] = 20
print(vars(a))
# {'x': 10, '_y': 20, '__z__': 5}

El uso dir(a)es un enfoque extraño, si no totalmente malo, para este problema. Es bueno si realmente necesita iterar sobre todos los atributos y métodos de la clase (incluidos los métodos especiales como __init__). Sin embargo, esto no parece ser lo que desea, e incluso la respuesta aceptada es pobre al aplicar un filtro frágil para tratar de eliminar métodos y dejar solo los atributos; puede ver cómo esto fallaría para la clase Adefinida anteriormente.

(el uso __dict__se ha realizado en un par de respuestas, pero todas definen métodos innecesarios en lugar de usarlo directamente. Solo un comentario sugiere usar vars).

Nathan
fuente
1
Algo que no he descubierto con el enfoque vars () es cómo manejar la situación en la que la clase A tiene un miembro que es otro objeto cuyo tipo es una clase B. definida por el usuario. Vars (a) parece llamar a __repr __ () en su miembro de tipo B. __repr __ (), según tengo entendido, se supone que devuelve una cadena. Pero cuando llamo vars (a), parece que tendría sentido que esta llamada devuelva un dict anidado, en lugar de un dict con una representación de cadena de B.
plafratt
1
Si tiene a.balguna clase personalizada, Bentonces vars(a)["b"] is a.b, como cabría esperar; nada más tendría sentido (piense a.ben azúcar sintáctico a.__dict__["b"]). Si tiene d = vars(a)y llama repr(d), llamará repr(d["b"])como parte de devolver su propia cadena de reproducción, ya que solo la clase Brealmente sabe cómo debe representarse como una cadena.
Nathan
2

Los objetos en python almacenan sus atributos (incluidas las funciones) en un dict llamado __dict__. Puede (pero generalmente no debería) usar esto para acceder a los atributos directamente. Si solo desea una lista, también puede llamar dir(obj), que devuelve un iterable con todos los nombres de atributos, que luego puede pasar getattr.

Sin embargo, la necesidad de hacer algo con los nombres de las variables suele ser un mal diseño. ¿Por qué no mantenerlos en una colección?

class Foo(object):
    def __init__(self, **values):
        self.special_values = values

Luego puede iterar sobre las teclas con for key in obj.special_values:

Daenyth
fuente
¿Puedes explicar un poco más cómo usaría la colección para lograr lo que quiero?
Pablo Mescher
1
No estoy agregando atributos al objeto dinámicamente. Las variables que tengo se mantendrán igual durante mucho tiempo, sin embargo, creo que sería bueno poder hacer algo como lo que pretendo hacer.
Pablo Mescher
1
class someclass:
        x=1
        y=2
        z=3
        def __init__(self):
           self.current_idx = 0
           self.items = ["x","y","z"]
        def next(self):
            if self.current_idx < len(self.items):
                self.current_idx += 1
                k = self.items[self.current_idx-1]
                return (k,getattr(self,k))
            else:
                raise StopIteration
        def __iter__(self):
           return self

entonces solo llámalo como iterable

s=someclass()
for k,v in s:
    print k,"=",v
Joran Beasley
fuente
Esto parece demasiado complicado. Si no tengo otra opción, prefiero seguir con lo que estoy haciendo ahora.
Pablo Mescher
0

La respuesta correcta a esto es que no deberías. Si desea este tipo de cosas, simplemente use un dict, o deberá agregar explícitamente atributos a algún contenedor. Puede automatizar eso aprendiendo sobre decoradores.

En particular, por cierto, método1 en su ejemplo es tan bueno de un atributo.

Julian
fuente
2
Sí, sé que no debería ... pero me ayuda a entender cómo funcionan las cosas. Vengo de un fondo de php y solía hacer este tipo de cosas a diario
Pablo Mescher
Me has convencido Mantener actualizado el método to_dict () es mucho más sencillo que cualquier respuesta hasta ahora.
Pablo Mescher
1
¿Quedan personas que se abstendrán de recibir consejos dogmáticos? La programación dinámica lo quemará si no tiene cuidado, pero las características están en el lenguaje que se utilizará.
Henrik Vendelbo
0

Para python 3.6

class SomeClass:

    def attr_list(self, should_print=False):

        items = self.__dict__.items()
        if should_print:
            [print(f"attribute: {k}    value: {v}") for k, v in items]

        return items
Pato de goma
fuente
66
¿Puedes agregar alguna explicación a tus publicaciones, para que también los pitonistas no profesionales puedan beneficiarse de tus respuestas?
not2qubit
-3

Por todos los fanáticos pitonianos que existen, estoy seguro de que Johan Cleeze aprobaría su dogmatismo;). Dejo esta respuesta y la sigo demoriendo. De hecho, me hace más confidente. Deja un comentario gallinas!

Para python 3.6

class SomeClass:

    def attr_list1(self, should_print=False):

        for k in self.__dict__.keys():
            v = self.__dict__.__getitem__(k)
            if should_print:
                print(f"attr: {k}    value: {v}")

    def attr_list(self, should_print=False):

        b = [(k, v) for k, v in self.__dict__.items()]
        if should_print:
            [print(f"attr: {a[0]}    value: {a[1]}") for a in b]
        return b
Pato de goma
fuente
¿Hay alguna razón por la que tienes dos respuestas a esta pregunta?
Nathan
@Nathan existe porque uno es más detallado y el otro más pitón. Tampoco sabía qué funciona más rápido, así que decidí dejar que el lector eligiera.
Rubber Duck
@nathan ¿eres Python o policía de desbordamiento de pila? Hay diferentes estilos de codificación. Prefiero uno no pitónico porque trabajo en muchos idiomas y tecnologías y creo que tiende a ser idiosincrásico. Sin embargo, la comprensión de Iist es fría y concisa. Entonces, ¿por qué no pagar al lector ilustrado?
Rubber Duck
Por todos los fanáticos pitonianos que hay, estoy seguro de que Johan Cleeze aprobaría su dogmatismo;). Dejo esta respuesta y la sigo demoriendo. De hecho, me hace más confidente. Deja un comentario gallinas!
Pato de goma