Itérer sur les attributs d'objet en python

162

J'ai un objet python avec plusieurs attributs et méthodes. Je veux parcourir les attributs d'objet.

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

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

Je veux générer un dictionnaire contenant tous les attributs des objets et leurs valeurs actuelles, mais je veux le faire de manière dynamique (donc si plus tard j'ajoute un autre attribut, je n'ai pas à me souvenir de mettre à jour ma fonction également).

En php, les variables peuvent être utilisées comme clés, mais les objets en python sont insuscriptibles et si j'utilise la notation par points pour cela, cela crée un nouvel attribut avec le nom de ma var, ce qui n'est pas mon intention.

Juste pour clarifier les choses:

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

Mise à jour: Avec les attributs, je veux dire uniquement les variables de cet objet, pas les méthodes.

Pablo Mescher
la source
3
stackoverflow.com/questions/1251692/ ... Cela pourrait vous aider.
sean
@sean En particulier, stackoverflow.com/a/1251789/4203 est la voie à suivre; vous utiliseriez l' predicateargument optionnel pour passer un appelable qui filtrerait les fonctions (liées?) (qui se débarrasserait des méthodes).
Hank Gay
Per sean, est-ce que cela répond à votre question? Comment énumérer les propriétés d'un objet en Python?
Davis Herring

Réponses:

227

En supposant que vous ayez un cours tel que

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

l'appel dirde l'objet vous rend tous les attributs de cet objet, y compris les attributs spéciaux python. Bien que certains attributs d'objet soient appelables, tels que les méthodes.

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

Vous pouvez toujours filtrer les méthodes spéciales en utilisant une compréhension de liste.

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

ou si vous préférez la carte / les filtres.

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

Si vous souhaitez filtrer les méthodes, vous pouvez utiliser la fonction intégrée callablecomme vérification.

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

Vous pouvez également inspecter la différence entre votre classe et son objet d'instance en utilisant.

>>> set(dir(Cls)) - set(dir(object))
set(['__module__', 'bar', 'func', '__dict__', 'foo', '__weakref__'])
Meitham
la source
1
C'est une mauvaise idée, je ne le suggérerais même pas, mais si c'est le cas, faites au moins une mise en garde.
Julian
2
@Meitham @Pablo Ce qui ne va pas, c'est surtout que vous ne devriez pas avoir à faire cela. Les attributs qui vous intéressent doivent être inclusifs et non exclusifs . Comme vous l'avez déjà vu, vous incluez des méthodes, et toute tentative de les exclure sera viciée, car elles impliqueront des choses désagréables comme callableou types.MethodType. Que se passe-t-il si j'ajoute un attribut appelé "_foo" qui stocke un résultat intermédiaire ou une structure de données interne? Que se passe-t-il si j'ajoute simplement un attribut à la classe de manière inoffensive name, disons, et maintenant, tout d'un coup, il est inclus.
Julian
3
@Julian le code ci-dessus choisira les deux _fooet nameparce qu'ils sont maintenant des attributs de l'objet. Si vous ne souhaitez pas les inclure, vous pouvez les exclure de la condition de compréhension de liste. Le code ci-dessus est un idiome python commun et un moyen populaire d'introspection des objets python.
Meitham
30
En fait, obj .__ dict__ est (probablement) meilleur dans ce but.
Dave
5
@Julian dir()ne "ment" que lorsqu'il est passé des métaclasses (un cas extrême) ou des classes avec des attributs décorés par @DynamicClassAttribute(un autre cas extrême). Dans les deux cas, l'appel du dirs()wrapper inspect.getmembers()résout ce problème. Pour les objets standard, cependant, l'approche de compréhension de liste de cette solution filtrant les non-appelables suffit absolument . Que certains pythonistes qualifient cela de "mauvaise idée" me déroute, mais ... à chacun le leur, je suppose?
Cecil Curry
57

en général, mettez une __iter__méthode dans votre classe et parcourez les attributs d'objet ou mettez cette classe mixin dans votre classe.

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

Ta 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
la source
cela ne fonctionne que lorsque oneet twoest défini après yc = YourClass(). La question porte sur la boucle à travers l'attris existante YourClass(). En outre, l'héritage de la IterMixinclasse peut ne pas toujours être disponible en tant que solution.
Xiao
4
vars(yc)donne le même résultat sans avoir à hériter d'une autre classe.
Nathan
1
Mon commentaire ci-dessus n'est pas tout à fait exact; vars(yc)est presque le même, mais le dictionnaire qu'il vous renvoie est le propre de l'instance __dict__, donc sa modification sera répercutée sur l'instance. Cela peut entraîner des problèmes si vous ne faites pas attention, donc la méthode ci-dessus peut parfois être meilleure (bien que copier le dictionnaire soit probablement plus facile). Notez également que bien que le dictionnaire renvoyé ci-dessus ne soit pas celui de l'instance __dict__, les valeurs sont les mêmes, donc la modification de toute valeur mutable sera toujours reflétée dans les attributs de l'instance.
Nathan
25

Comme mentionné dans certaines réponses / commentaires déjà, les objets Python stockent déjà un dictionnaire de leurs attributs (les méthodes ne sont pas incluses). Cela peut être consulté en tant que __dict__, mais la meilleure façon est d'utiliser vars(la sortie est la même, cependant). Notez que la modification de ce dictionnaire modifiera les attributs de l'instance! Cela peut être utile, mais cela signifie également que vous devez faire attention à la façon dont vous utilisez ce dictionnaire. Voici un exemple rapide:

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}

L'utilisation dir(a)est une approche étrange, sinon carrément mauvaise, de ce problème. C'est bien si vous aviez vraiment besoin d'itérer sur tous les attributs et méthodes de la classe (y compris les méthodes spéciales comme __init__). Cependant, cela ne semble pas être ce que vous voulez, et même la réponse acceptée y parvient mal en appliquant un filtrage fragile pour essayer de supprimer les méthodes et ne laisser que les attributs; vous pouvez voir comment cela échouerait pour la classe Adéfinie ci-dessus.

(l'utilisation __dict__a été faite dans quelques réponses, mais elles définissent toutes des méthodes inutiles au lieu de l'utiliser directement. Seul un commentaire suggère d'utiliser vars).

Nathan
la source
1
Quelque chose que je n'ai pas compris avec l'approche vars () est comment gérer la situation où la classe A a un membre qui est un autre objet dont le type est une classe définie par l'utilisateur B. vars (a) semble appeler __repr __ () sur son membre de type B. __repr __ (), si je comprends bien, est censé renvoyer une chaîne. Mais quand j'appelle vars (a), il semble qu'il serait logique que cet appel renvoie un dict imbriqué, au lieu d'un dict avec une représentation sous forme de chaîne de B.
plafratt
1
Si vous avez a.bune classe personnalisée Balors vars(a)["b"] is a.b, comme on peut s'y attendre; rien d'autre n'aurait vraiment de sens (pensez à du a.bsucre syntaxique pour a.__dict__["b"]). Si vous avez d = vars(a)et appelez, repr(d)il appellera repr(d["b"])dans le cadre du retour de sa propre chaîne de reprogrammation, car seule la classe Bsait vraiment comment elle doit être représentée sous forme de chaîne.
Nathan le
2

Les objets en python stockent leurs attributs (y compris les fonctions) dans un dict appelé __dict__. Vous pouvez (mais généralement pas) l'utiliser pour accéder directement aux attributs. Si vous voulez juste une liste, vous pouvez également appeler dir(obj), qui retourne un itérable avec tous les noms d'attribut, que vous pouvez ensuite passer getattr.

Cependant, avoir besoin de faire quoi que ce soit avec les noms des variables est généralement une mauvaise conception. Pourquoi ne pas les conserver dans une collection?

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

Vous pouvez ensuite parcourir les clés avec for key in obj.special_values:

Daenyth
la source
Pouvez-vous expliquer un peu plus comment utiliser la collection pour réaliser ce que je veux?
Pablo Mescher
1
Je n'ajoute pas d'attributs à l'objet de manière dynamique. Les variables que j'ai resteront les mêmes pendant longtemps, mais je pense que ce serait bien de pouvoir faire quelque chose comme ce que j'ai l'intention de faire.
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

alors appelez-le simplement comme un itérable

s=someclass()
for k,v in s:
    print k,"=",v
Joran Beasley
la source
Cela semble trop compliqué. Si je n'ai pas le choix, je préfère m'en tenir à ce que je fais en ce moment.
Pablo Mescher
0

La bonne réponse à cela est que vous ne devriez pas. Si vous voulez ce type de chose, utilisez simplement un dict ou vous devrez ajouter explicitement des attributs à certains conteneurs. Vous pouvez automatiser cela en vous renseignant sur les décorateurs.

En particulier, en passant, méthode1 dans votre exemple est tout aussi bonne d'un attribut.

julien
la source
2
Ouais, je sais que je ne devrais pas… mais ça m'aide à comprendre comment les choses fonctionnent. Je viens d'un fond php et j'ai l'habitude de faire ce genre de choses quotidiennement
Pablo Mescher
Vous m'avez convaincu. Garder la méthode to_dict () à jour est beaucoup plus simple que n'importe quelle réponse jusqu'à présent.
Pablo Mescher
1
Reste-t-il des personnes qui s'abstiendront de tout conseil dogmatique? La programmation dynamique vous brûlera si vous ne faites pas attention, mais les fonctionnalités sont dans le langage à utiliser.
Henrik Vendelbo
0

Pour 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
Canard en caoutchouc
la source
6
Pouvez-vous ajouter des explications à vos messages, afin que les pythonistes non professionnels puissent également bénéficier de vos réponses?
not2qubit
-3

Pour tous les fanatiques pythoniens, je suis sûr que Johan Cleeze approuverait votre dogmatisme;). Je laisse cette réponse continuer à la démériter. Cela me rend en fait plus confidentiel. Laissez un commentaire vous les poulets!

Pour 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
Canard en caoutchouc
la source
Y a-t-il une raison pour laquelle vous avez deux réponses à cette question?
Nathan
@Nathan il y en a parce que l'un est plus verbeux et l'autre plus pythonien. Je ne savais pas non plus ce qui fonctionnait le plus vite, alors j'ai décidé de laisser le lecteur choisir.
Rubber Duck
@nathan êtes-vous python ou police de débordement de pile? Il existe différents styles de codage. Je préfère une version non pythonique car je travaille dans de nombreux langages et technologies et je pense qu'elle a tendance à être idiosyncratique. Néanmoins, la compréhension de Iist est fraîche et concise. Alors pourquoi ne pas offrir les deux au lecteur éclairé?
Rubber Duck
Pour tous les fanatiques pythoniens, je suis sûr que Johan Cleeze approuverait votre dogmatisme;). Je laisse cette réponse continuer à la démériter. Cela me rend en fait plus confidentiel. Laissez un commentaire vous les poulets!
Rubber Duck le