Iterar sobre atributos de objeto em python

162

Eu tenho um objeto python com vários atributos e métodos. Eu quero iterar sobre os atributos do objeto.

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

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

Quero gerar um dicionário que contenha todos os atributos dos objetos e seus valores atuais, mas quero fazê-lo de maneira dinâmica (se mais tarde adicionar outro atributo, não preciso me lembrar de atualizar também minha função).

No php, variáveis ​​podem ser usadas como chaves, mas os objetos em python são indescritíveis e, se eu usar a notação de ponto, cria um novo atributo com o nome da minha var, que não é minha intenção.

Apenas para esclarecer as coisas:

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

Atualização: com atributos, quero dizer apenas as variáveis ​​deste objeto, não os métodos.

Pablo Mescher
fonte
3
stackoverflow.com/questions/1251692/… Pode ser de alguma ajuda.
Sean
@sean Em particular, stackoverflow.com/a/1251789/4203 é o caminho a percorrer; você usaria o predicateargumento opcional para transmitir uma chamada que filtraria as funções (ligadas?) (que se livraria dos métodos).
Hank Gay
Por Sean, isso responde à sua pergunta? Como enumerar as propriedades de um objeto em Python?
Davis Herring

Respostas:

227

Supondo que você tenha uma classe como

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

chamar diro objeto devolve todos os atributos desse objeto, incluindo atributos especiais do python. Embora alguns atributos do objeto possam ser chamados, como 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']

Você sempre pode filtrar os métodos especiais usando uma compreensão de lista.

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

ou se você preferir mapas / filtros.

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

Se você deseja filtrar os métodos, pode usar o built-in callablecomo uma verificação.

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

Você também pode inspecionar a diferença entre sua classe e seu objeto de instância usando.

>>> set(dir(Cls)) - set(dir(object))
set(['__module__', 'bar', 'func', '__dict__', 'foo', '__weakref__'])
Meitham
fonte
1
Essa é uma péssima idéia, eu nem sugeriria, mas se você for, pelo menos forneça a ressalva.
Julian
2
@Meitham @Pablo O que há de errado é principalmente que você não precisa fazer isso. Quais atributos em que você está interessado devem ser inclusivos e não exclusivos . Como você já viu, você está incluindo métodos, e qualquer tentativa de excluí-los será falha, porque eles envolverão coisas desagradáveis ​​como callableou types.MethodType. O que acontece se eu adicionar um atributo chamado "_foo" que armazene algum resultado intermediário ou estrutura de dados interna? O que acontece se eu adicionar inofensivamente um atributo à classe name, digamos, e agora, de repente, ele for incluído.
Julian
3
@ Julian, o código acima selecionará os dois _fooe nameporque agora eles são atributos do objeto. Se você não deseja incluí-los, pode excluí-los na condição de compreensão da lista. O código acima é um idioma comum do python e uma maneira popular de examinar objetos python.
Meitham 24/07/12
30
Na verdade, obj .__ dict__ é (provavelmente) melhor para esse fim.
24412 Dave Dave
5
@Julian dir()apenas "mente" quando as metaclasses são aprovadas (um caso extremo) ou classes com atributos decorados por @DynamicClassAttribute(outro caso extremo). Nos dois casos, chamar o dirs()wrapper inspect.getmembers()resolve isso. Para objetos padrão, no entanto, a abordagem de compreensão da lista desta solução é absolutamente filtrada. O fato de alguns pythonistas classificá-la como uma "má idéia" me confunde, mas ... cada um na sua, suponho?
22416 Cecil Curry
57

em geral, coloque um __iter__método em sua classe e repita os atributos do objeto ou coloque essa classe mixin em sua classe.

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

Sua classe:

>>> 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
fonte
isso só funciona quando onee twoé definido depois yc = YourClass(). A pergunta é sobre o loop através dos atributos existentes YourClass(). Além disso, a herança da IterMixinclasse nem sempre está disponível como solução.
Xiao
4
vars(yc)dá o mesmo resultado sem precisar herdar de outra classe.
Nathan
1
Meu comentário acima não está certo; vars(yc)é quase o mesmo, mas o dicionário retornado é o da instância __dict__, portanto, a modificação será refletida na instância. Isso pode causar problemas se você não tomar cuidado, portanto o método acima pode ser melhor (às vezes, é mais fácil copiar o dicionário). Além disso, observe que, embora o dicionário retornado acima não seja da instância __dict__, os valores são os mesmos, portanto, a modificação de quaisquer valores mutáveis ​​ainda será refletida nos atributos da instância.
Nathan
25

Como já mencionado em algumas respostas / comentários, os objetos Python já armazenam um dicionário de seus atributos (os métodos não estão incluídos). Isso pode ser acessado como __dict__, mas a melhor maneira é usar vars(a saída é a mesma). Observe que a modificação deste dicionário modificará os atributos na instância! Isso pode ser útil, mas também significa que você deve ter cuidado com a forma como usa este dicionário. Aqui está um exemplo 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}

Usar dir(a)é uma abordagem estranha, se não totalmente ruim, para esse problema. É bom se você realmente precisa iterar sobre todos os atributos e métodos da classe (incluindo métodos especiais como __init__). No entanto, isso não parece ser o que você deseja, e até a resposta aceita é mal aplicada aplicando uma filtragem frágil para tentar remover métodos e deixar apenas os atributos; você pode ver como isso falharia para a classe Adefinida acima.

(o uso __dict__foi feito em algumas respostas, mas todos definem métodos desnecessários em vez de usá-lo diretamente. Apenas um comentário sugere o uso vars).

Nathan
fonte
1
Algo que ainda não descobri com a abordagem vars () é como lidar com a situação em que a classe A tem um membro que é outro objeto cujo tipo é uma classe B. definida pelo usuário. Vars (a) parece chamar __repr __ () em seu membro do tipo B. __repr __ (), como eu entendo, deve retornar uma string. Mas quando eu chamo vars (a), parece que faria sentido para essa chamada retornar um ditado aninhado, em vez de um ditado com uma representação de seqüência de caracteres B.
plafratt
1
Se você tem a.buma classe personalizada , Bentão vars(a)["b"] is a.b, como seria de esperar; nada mais faria sentido (pense a.bno açúcar sintático a.__dict__["b"]). Se você tiver d = vars(a)e chamar repr(d), ele chamará repr(d["b"])como parte do retorno de sua própria repr-string, pois apenas a classe Brealmente sabe como deve ser representada como uma string.
Nathan
2

Objetos em python armazenam seus atributos (incluindo funções) em um ditado chamado __dict__. Você pode (mas geralmente não deveria) usar isso para acessar os atributos diretamente. Se você deseja apenas uma lista, também pode chamar dir(obj), que retorna uma iterável com todos os nomes de atributos, para os quais você poderia passar getattr.

No entanto, a necessidade de fazer qualquer coisa com os nomes das variáveis ​​geralmente é um design ruim. Por que não mantê-los em uma coleção?

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

Em seguida, você pode percorrer as teclas com for key in obj.special_values:

Daenyth
fonte
Você pode explicar um pouco mais como eu usaria a coleção para alcançar o que quero?
Pablo Mescher 24/07/2012
1
Não estou adicionando atributos ao objeto dinamicamente. As variáveis ​​que eu tenho permanecerão as mesmas por muito tempo, no entanto, acho que seria bom poder fazer algo parecido com o que pretendo.
Pablo Mescher 24/07
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

então chame isso de iterável

s=someclass()
for k,v in s:
    print k,"=",v
Joran Beasley
fonte
Isso parece muito complicado. Se não tenho escolha, prefiro continuar com o que estou fazendo agora.
Pablo Mescher 24/07
0

A resposta correta para isso é que você não deveria. Se você deseja esse tipo de coisa, basta usar um dict, ou precisará adicionar explicitamente atributos a algum contêiner. Você pode automatizar isso aprendendo sobre decoradores.

Em particular, a propósito, o método1 no seu exemplo é tão bom quanto um atributo.

Julian
fonte
2
Sim, eu sei que não deveria ... mas isso me ajuda a entender como as coisas funcionam. Eu venho de um fundo php e usado para fazer este tipo de coisas diária
Pablo Mescher
Você me convenceu. Manter o método to_dict () atualizado é muito mais direto do que qualquer resposta até o momento.
Pablo Mescher
1
Restam pessoas que se absterão de conselhos dogmáticos. A programação dinâmica o queimará se você não tomar cuidado, mas os recursos estão no idioma a ser usado.
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 borracha
fonte
6
Você pode adicionar alguma explicação às suas postagens, para que também pythonists não profissionais possam se beneficiar de suas respostas?
Not2qubit 6/1018
-3

Por todos os fanáticos pitonianos por aí, tenho certeza de que Johan Cleeze aprovaria seu dogmatismo;). Estou deixando esta resposta, continuando a deméritá-la. Isso realmente me deixa mais confidente. Deixe um comentário, galinhas!

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 borracha
fonte
Existe uma razão para você ter duas respostas para esta pergunta?
Nathan
@ Nathan existe porque um é mais detalhado e o outro mais pythoniano. Eu também não sabia o que corre mais rápido, então decidi deixar o leitor escolher.
Rubber Duck
@nathan você é python ou stack overflow police? Existem diferentes estilos de codificação. Prefiro um não pitonico porque trabalho em muitas linguagens e tecnologias e acho que tende a ser idiossincrático. No entanto, a compreensão da lista é fria e concisa. Então, por que não pagar tanto ao leitor esclarecido?
Rubber Duck
Por todos os fanáticos pythonianos por aí, tenho certeza de que Johan Cleeze aprovaria seu dogmatismo;). Estou deixando esta resposta, continuando a deméritá-la. Isso realmente me deixa mais confidente. Deixe um comentário, galinhas!
Rubber Duck