Iteruj po atrybutach obiektów w Pythonie

162

Mam obiekt Pythona z kilkoma atrybutami i metodami. Chcę iterować po atrybutach obiektów.

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

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

Chcę wygenerować słownik zawierający wszystkie atrybuty obiektów i ich bieżące wartości, ale chcę to zrobić w sposób dynamiczny (więc jeśli później dodam kolejny atrybut, nie muszę również pamiętać o aktualizacji mojej funkcji).

W php zmienne mogą być używane jako klucze, ale obiekty w Pythonie nie są obsługiwane i jeśli używam do tego notacji z kropką, tworzy nowy atrybut o nazwie moja var, co nie jest moim zamiarem.

Żeby było jaśniej:

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

Aktualizacja: Przez atrybuty mam na myśli tylko zmienne tego obiektu, a nie metody.

Pablo Mescher
źródło
3
stackoverflow.com/questions/1251692/… Może być jakaś pomoc.
sean
@sean W szczególności, stackoverflow.com/a/1251789/4203 jest drogą do zrobienia; użyłbyś opcjonalnego predicateargumentu, aby przekazać wywoływalną funkcję, która odfiltrowałaby (ograniczyłaby?) funkcje (która pozbyłaby się metod).
Hank Gay
Czy to odpowiada na twoje pytanie? Jak wyliczyć właściwości obiektu w Pythonie?
Davis Herring

Odpowiedzi:

227

Zakładając, że masz klasę taką jak

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

wywołanie dirobiektu zwraca wszystkie atrybuty tego obiektu, w tym specjalne atrybuty Pythona. Chociaż niektóre atrybuty obiektów są wywoływalne, takie jak metody.

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

Możesz zawsze odfiltrować specjalne metody, używając rozumienia listy.

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

lub jeśli wolisz mapy / filtry.

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

Jeśli chcesz odfiltrować metody, możesz użyć wbudowanego callablejako sprawdzenia.

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

Możesz również sprawdzić różnicę między twoją klasą a jej obiektem instancji przy użyciu.

>>> set(dir(Cls)) - set(dir(object))
set(['__module__', 'bar', 'func', '__dict__', 'foo', '__weakref__'])
Meitham
źródło
1
To zły pomysł, nawet bym tego nie sugerował, ale jeśli masz zamiar, przynajmniej zastanów się.
Julian
2
@Meitham @Pablo Problem polega głównie na tym, że nie powinieneś tego robić. Które atrybuty, które Cię interesują, powinny obejmować wszystkie, a nie wykluczać . Jak już widzieliście, uwzględniacie metody, a każda próba ich wykluczenia będzie błędna, ponieważ będą obejmować nieprzyjemne rzeczy, takie jak callablelub types.MethodType. Co się stanie, jeśli dodam atrybut o nazwie „_foo”, który przechowuje jakiś wynik pośredni lub wewnętrzną strukturę danych? Co się stanie, jeśli po prostu nieszkodliwie dodam atrybut do klasy name, powiedzmy a, a teraz nagle zostanie on uwzględniony.
Julian
3
@Julian powyższy kod wybierze oba _fooi nameponieważ są one teraz atrybutami obiektu. Jeśli nie chcesz ich uwzględniać, możesz wykluczyć je z warunku rozumienia listy. Powyższy kod jest popularnym idiomem Pythona i popularnym sposobem introspekcji obiektów Pythona.
Meitham
30
Właściwie obj .__ dict__ jest (prawdopodobnie) lepszy do tego celu.
Dave,
5
@Julian dir()"kłamie" tylko wtedy, gdy przeszedł metaklasy (skrajny przypadek krawędzi) lub klasy z atrybutami ozdobionymi przez @DynamicClassAttribute(kolejny skrajny przypadek skrajny). W obu przypadkach wywołanie dirs()opakowania inspect.getmembers()rozwiązuje ten problem. Jednak w przypadku obiektów standardowych absolutnie wystarczające jest podejście oparte na zrozumieniu list w tym rozwiązaniu, odfiltrowując elementy niewymienialne . To, że niektórzy Pythonistas określiliby to jako „zły pomysł”, wprawia mnie w zakłopotanie, ale ... dla każdego z nich, jak sądzę?
Cecil Curry
57

generalnie umieść __iter__metodę w swojej klasie i iteruj przez atrybuty obiektu lub umieść tę klasę mixin w swojej klasie.

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

Twoja klasa:

>>> 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
źródło
działa to tylko wtedy, gdy onei twojest zdefiniowane później yc = YourClass(). Pytanie dotyczy przeglądania istniejących attris w YourClass(). Ponadto dziedziczenie IterMixinklasy może nie zawsze być dostępne jako rozwiązanie.
Xiao
4
vars(yc)daje ten sam wynik bez konieczności dziedziczenia z innej klasy.
Nathan
1
Mój powyższy komentarz nie jest całkiem poprawny; vars(yc)jest prawie taki sam, ale słownik, który Ci zwraca, należy do instancji __dict__, więc zmodyfikowanie go zostanie odzwierciedlone w instancji. Może to prowadzić do problemów, jeśli nie będziesz ostrożny, więc powyższa metoda może czasami być lepsza (chociaż skopiowanie słownika jest prawdopodobnie łatwiejsze). Zwróć również uwagę, że chociaż słownik zwrócony powyżej nie należy do instancji __dict__, wartości są takie same, więc modyfikowanie wszelkich zmiennych wartości będzie nadal odzwierciedlane w atrybutach instancji.
Nathan,
25

Jak już wspomniano w niektórych odpowiedziach / komentarzach, obiekty Pythona już przechowują słownik ich atrybutów (metody nie są uwzględnione). Można to uzyskać w postaci __dict__, ale lepszym sposobem jest użycie vars(dane wyjściowe są jednak takie same). Zwróć uwagę, że modyfikowanie tego słownika spowoduje zmodyfikowanie atrybutów instancji! Może to być przydatne, ale oznacza również, że powinieneś ostrożnie korzystać z tego słownika. Oto krótki przykład:

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}

Używanie dir(a)jest dziwnym, jeśli nie wręcz złym podejściem do tego problemu. Dobrze, jeśli naprawdę potrzebujesz iteracji po wszystkich atrybutach i metodach klasy (w tym metodach specjalnych, takich jak __init__). Jednak wydaje się, że nie jest to to, czego chcesz, a nawet zaakceptowana odpowiedź idzie źle, stosując trochę kruchego filtrowania, aby spróbować usunąć metody i pozostawić tylko atrybuty; możesz zobaczyć, jak to się nie powiedzie dla klasy Azdefiniowanej powyżej.

(Używanie __dict__zostało zrobione w kilku odpowiedziach, ale wszystkie definiują niepotrzebne metody zamiast używać ich bezpośrednio. Jedynie komentarz sugeruje użycie vars).

Nathan
źródło
1
Coś, czego nie odkryłem z podejściem vars (), to jak sobie poradzić z sytuacją, w której klasa A ma członka będącego innym obiektem, którego typem jest klasa zdefiniowana przez użytkownika B. vars (a) wydaje się wywoływać __repr __ () na swoim składniku typu B. __repr __ (), jak rozumiem, ma zwrócić ciąg. Ale kiedy wywołuję vars (a), wydaje się, że sensowne byłoby, aby to wywołanie
zwróciło
1
Jeśli masz a.bw pewnym niestandardowej klasy Bnastępnie vars(a)["b"] is a.b, jak można by się spodziewać; nic innego nie miałoby sensu (myśleć o a.bcukrze syntaktycznym a.__dict__["b"]). Jeśli masz d = vars(a)i wywołasz, repr(d)to wywoła repr(d["b"])jako część zwracania własnego ciągu repr, ponieważ tylko klasa Bnaprawdę wie, jak powinien być reprezentowany jako łańcuch.
Nathan
2

Obiekty w Pythonie przechowują swoje atrybuty (w tym funkcje) w formie dict __dict__. Możesz (ale generalnie nie powinieneś) tego używać do bezpośredniego dostępu do atrybutów. Jeśli potrzebujesz tylko listy, możesz również wywołać dir(obj), co zwraca iterowalną ze wszystkimi nazwami atrybutów, do których możesz następnie przekazać getattr.

Jednak konieczność robienia czegokolwiek z nazwami zmiennych jest zwykle złym projektem. Dlaczego nie przechowywać ich w kolekcji?

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

Następnie możesz iterować po kluczach za pomocą for key in obj.special_values:

Daenyth
źródło
Czy możesz trochę więcej wyjaśnić, w jaki sposób wykorzystałbym tę kolekcję, aby osiągnąć to, czego chcę?
Pablo Mescher
1
Nie dodaję atrybutów do obiektu dynamicznie. Zmienne, które mam, pozostaną takie same przez długi czas, ale myślę, że fajnie byłoby móc zrobić coś takiego, co zamierzam.
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

następnie nazwij to iterowalnym

s=someclass()
for k,v in s:
    print k,"=",v
Joran Beasley
źródło
Wydaje się to zbyt skomplikowane. Jeśli nie mam wyboru, wolałbym trzymać się tego, co teraz robię.
Pablo Mescher
0

Prawidłowa odpowiedź brzmi: nie powinieneś. Jeśli chcesz tego typu rzeczy, użyj po prostu dyktu lub będziesz musiał jawnie dodać atrybuty do jakiegoś kontenera. Możesz to zautomatyzować, ucząc się o dekoratorach.

W szczególności, nawiasem mówiąc, metoda method1 w twoim przykładzie jest równie dobra jak atrybut.

juliański
źródło
2
Tak, wiem, że nie powinienem ... ale to pomaga mi zrozumieć, jak to działa. Pochodzę z tła php i używane do tego rodzaju rzeczy codziennie
Pablo Mescher
Przekonałeś mnie. Utrzymywanie aktualizacji metody to_dict () jest znacznie prostsze niż jakakolwiek dotychczasowa odpowiedź.
Pablo Mescher
1
Czy są jeszcze ludzie, którzy powstrzymają się od dogmatycznych rad? Programowanie dynamiczne będzie cię palić, jeśli nie będziesz ostrożny, ale funkcje są w języku, którego chcesz używać.
Henrik Vendelbo
0

W przypadku Pythona 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
Gumowa kaczuszka
źródło
6
Czy możesz dodać wyjaśnienie do swoich postów, aby również nieprofesjonalni pytoniści mogli skorzystać z Twoich odpowiedzi?
not2qubit
-3

Jestem pewien, że Johan Cleeze pochwaliłby twój dogmatyzm;). Zostawiam tę odpowiedź, wciąż ją obwiniaj To właściwie czyni mnie bardziej powiernym. Zostaw komentarz kurczaki!

W przypadku Pythona 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
Gumowa kaczuszka
źródło
Czy istnieje powód, dla którego masz dwie odpowiedzi na to pytanie?
Nathan
@Nathan jest, ponieważ jeden jest bardziej szczegółowy, a drugi bardziej pytoński. Nie wiedziałem też, który działa szybciej, więc postanowiłem pozwolić czytelnikowi wybrać.
Rubber Duck,
@nathan, jesteś Pythonem czy policją przepełnienia stosu? Istnieją różne style kodowania. Wolę język inny niż Python, ponieważ pracuję w wielu językach i technologiach i myślę, że jest to specyficzne. Niemniej jednak zrozumienie listy jest chłodne i zwięzłe. Dlaczego więc nie pozwolić oświeconemu czytelnikowi na jedno i drugie?
Rubber Duck,
Jestem pewien, że Johan Cleeze pochwaliłby twój dogmatyzm;). Zostawiam tę odpowiedź, wciąż ją obwiniaj To właściwie czyni mnie bardziej powiernym. Zostaw komentarz kurczaki!
Rubber Duck