Scorrere gli attributi degli oggetti in Python


162

Ho un oggetto Python con diversi attributi e metodi. Voglio scorrere gli attributi degli oggetti.

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

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

Voglio generare un dizionario contenente tutti gli attributi degli oggetti e i loro valori correnti, ma voglio farlo in modo dinamico (quindi se in seguito aggiungo un altro attributo non devo ricordare di aggiornare anche la mia funzione).

In php le variabili possono essere usate come chiavi, ma gli oggetti in Python sono insuperabili e se uso la notazione punto per questo crea un nuovo attributo con il nome del mio var, che non è il mio intento.

Giusto per chiarire le cose:

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

Aggiornamento: Con attributi intendo solo le variabili di questo oggetto, non i metodi.


3
stackoverflow.com/questions/1251692/… Potrebbe esserti di aiuto.
sean

@sean In particolare, stackoverflow.com/a/1251789/4203 è la strada da percorrere; useresti l' predicateargomento opzionale per passare un callable che filtrerebbe le funzioni (associate?) (che eliminano i metodi).
Hank Gay,

Per sean, questo risponde alla tua domanda? Come enumerare le proprietà di un oggetto in Python?
Davis Herring,

Risposte:


227

Supponendo che tu abbia una classe come

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

richiamare dirl'oggetto ti restituisce tutti gli attributi di quell'oggetto, inclusi gli attributi speciali di Python. Sebbene alcuni attributi degli oggetti siano richiamabili, come i metodi.

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

Puoi sempre filtrare i metodi speciali usando una comprensione dell'elenco.

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

o se preferisci mappa / filtri.

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

Se si desidera filtrare i metodi, è possibile utilizzare l'integrato callablecome controllo.

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

Puoi anche ispezionare la differenza tra la tua classe e il suo oggetto di istanza usando.

>>> set(dir(Cls)) - set(dir(object))
set(['__module__', 'bar', 'func', '__dict__', 'foo', '__weakref__'])

1
Questa è una cattiva idea, non la suggerirei nemmeno, ma se hai intenzione di fare, almeno fornire un avvertimento.
Julian,

2
@Meitham @Pablo Quello che non va è principalmente che non dovresti aver bisogno di farlo. Quali attributi ti interessano dovrebbero essere inclusivi, non esclusivi . Come hai già visto, stai includendo metodi e qualsiasi tentativo di escluderli sarà imperfetto, perché coinvolgeranno cose cattive come callableo types.MethodType. Cosa succede se aggiungo un attributo chiamato "_foo" che memorizza un risultato intermedio o una struttura di dati interna? Cosa succede se aggiungo innocuamente un attributo alla classe, un name, diciamo, e ora all'improvviso viene incluso.
Julian,

3
@Julian il codice sopra selezionerà entrambi _fooe nameperché ora sono attributi dell'oggetto. Se non si desidera includerli, è possibile escluderli dalla condizione di comprensione dell'elenco. Il codice sopra è un linguaggio comune di Python e un modo popolare di introspezione di oggetti Python.
Meitham,

30
In realtà obj .__ dict__ è (probabilmente) migliore per questo scopo.
Dave,

5
@Julian dir()" mente " solo quando vengono superate le metaclasse (un caso limite estremo) o le classi con attributi decorati da @DynamicClassAttribute(un altro caso limite estremo). In entrambi i casi, chiamare il dirs()wrapper inspect.getmembers()risolve questo. Per gli oggetti standard, tuttavia, l'approccio di comprensione dell'elenco di questa soluzione che filtra i non-callable è assolutamente sufficiente. Il fatto che alcuni pitonisti lo etichettino come una "cattiva idea" mi sconcerta, ma ... a ciascuno il suo, suppongo?
Cecil Curry,

57

in generale metti un __iter__metodo nella tua classe e fai scorrere gli attributi dell'oggetto o metti questa classe di mixin nella tua classe.

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

La tua 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'}

funziona solo quando onee twoviene definito dopo yc = YourClass(). La domanda si pone sulla ripetizione dell'attrito esistente YourClass(). Inoltre, ereditare la IterMixinclasse potrebbe non essere sempre disponibile come soluzione.
Xiao,

4
vars(yc)dà lo stesso risultato senza dover ereditare da un'altra classe.
Nathan,

1
Il mio commento sopra non è del tutto corretto; vars(yc)è quasi lo stesso, ma il dizionario che ti restituisce è l'istanza __dict__, quindi la modifica si rifletterà sull'istanza. Questo può portare a problemi se non stai attento, quindi il metodo sopra potrebbe talvolta essere migliore (anche se la copia del dizionario è probabilmente più semplice). Inoltre, tieni presente che mentre il dizionario restituito sopra non è quello dell'istanza __dict__, i valori sono gli stessi, quindi la modifica di qualsiasi valore modificabile si rifletterà ancora negli attributi dell'istanza.
Nathan,

25

Come già menzionato in alcune risposte / commenti, gli oggetti Python memorizzano già un dizionario dei loro attributi (i metodi non sono inclusi). È possibile accedervi __dict__, ma il modo migliore è usare vars(l'output è lo stesso, però). Nota che la modifica di questo dizionario modificherà gli attributi sull'istanza! Questo può essere utile, ma significa anche che dovresti stare attento a come usi questo dizionario. Ecco un breve esempio:

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'uso dir(a)è un approccio strano, se non addirittura negativo, a questo problema. È buono se hai davvero bisogno di iterare su tutti gli attributi e metodi della classe (compresi i metodi speciali come __init__). Tuttavia, questo non sembra essere quello che vuoi, e anche la risposta accettata lo fa male applicando un filtro fragile per provare a rimuovere i metodi e lasciare solo gli attributi; puoi vedere come ciò fallirebbe per la classe Asopra definita.

(l'utilizzo __dict__è stato fatto in un paio di risposte, ma tutti definiscono metodi non necessari invece di usarlo direttamente. Solo un commento suggerisce di usare vars).


1
Qualcosa che non ho capito con l'approccio vars () è come gestire la situazione in cui la classe A ha un membro che è un altro oggetto il cui tipo è una classe B. definita dall'utente vars (a) sembra chiamare __repr __ () sul suo membro di tipo B. __repr __ (), a quanto ho capito, dovrebbe restituire una stringa. Ma quando chiamo var (a), sembra che avrebbe senso per questa chiamata restituire un dict nidificato, anziché un dict con una rappresentazione in stringa di B.
plafratt

1
Se hai a.buna classe personalizzata Ballora vars(a)["b"] is a.b, come ci si aspetterebbe; nient'altro avrebbe davvero senso (pensate a a.bcome zucchero sintattico per a.__dict__["b"]). Se lo hai d = vars(a)e lo chiamerai repr(d), chiamerà repr(d["b"])come parte del ritorno della propria stringa repr poiché solo la classe Bsa davvero come dovrebbe essere rappresentata come una stringa.
Nathan,

2

Gli oggetti in Python memorizzano i loro attributi (comprese le funzioni) in un dict chiamato __dict__. Puoi (ma generalmente non dovresti) usarlo per accedere direttamente agli attributi. Se vuoi solo un elenco, puoi anche chiamare dir(obj), che restituisce un iterabile con tutti i nomi degli attributi, a cui potresti passare getattr.

Tuttavia, la necessità di fare qualcosa con i nomi delle variabili è di solito una cattiva progettazione. Perché non tenerli in una raccolta?

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

È quindi possibile scorrere le chiavi con for key in obj.special_values:


Puoi spiegarci un po 'di più come userei la collezione per ottenere ciò che voglio?
Pablo Mescher,

1
Non sto aggiungendo gli attributi all'oggetto in modo dinamico. Le variabili che ho rimarranno le stesse per molto tempo, tuttavia penso che sarebbe bello poter fare qualcosa di simile a ciò che intendo.
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

quindi chiamalo semplicemente come iterabile

s=someclass()
for k,v in s:
    print k,"=",v

Questo sembra troppo complicato. Se non ho scelta, preferirei attenermi a quello che sto facendo in questo momento.
Pablo Mescher,

0

La risposta corretta a questo è che non dovresti. Se vuoi questo tipo di cose o usa semplicemente un dict, o dovrai aggiungere esplicitamente degli attributi ad alcuni container. Puoi automatizzarlo imparando a conoscere i decoratori.

In particolare, a proposito, method1 nel tuo esempio è altrettanto valido di un attributo.


2
Sì, lo so che non dovrei ... ma mi aiuta a capire come funzionano le cose. Vengo da un background php e
facevo

Mi hai convinto Mantenere aggiornato il metodo to_dict () è molto più semplice di qualsiasi risposta finora.
Pablo Mescher,

1
Sono rimaste persone che si asterranno da consigli dogmatici. La programmazione dinamica ti brucerà se non stai attento, ma le funzionalità sono nella lingua da utilizzare.
Henrik Vendelbo,

0

Per 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

6
Puoi aggiungere qualche spiegazione ai tuoi post, in modo che anche i pitonisti non professionisti possano trarre vantaggio dalle tue risposte?
not2qubit

-3

Per tutti i fanatici pitoni là fuori sono sicuro che Johan Cleeze approverebbe il tuo dogmatismo;). Lascio questa risposta continuando a demeritarla In realtà mi rende più confidente. Lascia un commento polli!

Per 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

C'è una ragione per cui hai due risposte su questa domanda?
Nathan

@Nathan c'è perché uno è più prolisso e l'altro più pitone. Inoltre non sapevo quale corre più veloce, quindi ho deciso di lasciare scegliere al lettore.
Rubber Duck,

@nathan sei una polizia di pit-over o overflow dello stack? Esistono diversi stili di codifica. Preferisco uno non pitonico perché lavoro in molti linguaggi e tecnologie e penso che tende ad essere idiosincratico. Tuttavia la comprensione dell'Iist è interessante e concisa. Quindi perché non permettersi entrambi i lettori illuminati?
Rubber Duck,

Per tutti i fanatici pitoni là fuori sono sicuro che Johan Cleeze approverebbe il tuo dogmatismo;). Lascio questa risposta continuando a demeritarla In realtà mi rende più confidente. Lascia un commento polli!
Rubber Duck,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.