Ітерація над атрибутами об'єктів у python


162

У мене є об’єкт python з кількома атрибутами та методами. Я хочу перебрати атрибути об'єкта.

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

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

Я хочу створити словник, що містить усі атрибути об'єктів та їх поточні значення, але я хочу це зробити динамічним способом (тому, якщо пізніше я додаю ще один атрибут, мені не потрібно пам’ятати, щоб також оновити свою функцію).

У php змінні можуть використовуватися як ключі, але об'єкти в python не піддаються написанню, і якщо я використовую для цього позначення крапок, він створює новий атрибут з іменем мого var, що не є моїм наміром.

Просто щоб зробити речі зрозумілішими:

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

Оновлення: Під атрибутами я маю на увазі лише змінні цього об'єкта, а не методи.


3
stackoverflow.com/questions/1251692/… Можливо, вам допоможуть.
sean

@sean Зокрема, stackoverflow.com/a/1251789/4203 це шлях; ви б використовували необов'язковий predicateаргумент для передачі дзвінка, який би фільтрував (прив'язував?) функції (що позбавляється від методів).
Хенк Гей

Відповіді:


227

Якщо припустити, що у вас є такий клас, як

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

виклик dirоб’єкта повертає всі атрибути цього об’єкта, включаючи спеціальні атрибути python. Хоча деякі атрибути об'єкта можна викликати, наприклад, методи.

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

Ви завжди можете відфільтрувати спеціальні методи, використовуючи розуміння списку.

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

або якщо ви віддаєте перевагу карту / фільтри.

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

Якщо ви хочете відфільтрувати методи, ви можете використовувати вбудований callableв якості перевірки.

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

Ви також можете перевірити різницю між вашим класом та його об'єктом, використовуючи.

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

1
Це погана ідея, я б навіть не пропонував її, але якщо ви збираєтесь, принаймні надайте застереження.
Джуліан

2
@Meitham @Pablo Що не так, головним чином, вам це не потрібно робити. Які атрибути, які вас цікавлять, повинні включати не виключно . Як ви вже бачили, ви включаєте методи, і будь-яка спроба їх виключити буде помилковою, оскільки вони будуть стосуватися неприємних речей, таких як callableабо types.MethodType. Що станеться, якщо я додаю атрибут під назвою "_foo", який зберігає проміжний результат або внутрішню структуру даних? Що станеться, якщо я просто нешкідливо додаю атрибут до класу name, скажімо, і тепер раптом він включається.
Джуліан

3
@Julian наведений вище код вибере обидва _fooі nameтому, що вони тепер є атрибутами об'єкта. Якщо ви не хочете їх включати, ви можете виключити їх зі списку умови розуміння. Код, наведений вище, є загальною ідіомою пітона та популярним способом самоаналізу об’єктів пітона.
Meitham

30
Насправді obj .__ dict__ (мабуть) краще для цієї мети.
Дейв

5
@Julian dir()"лежить" лише тоді, коли передаються метакласи (крайній крайній випадок) або класи з атрибутами, прикрашеними @DynamicClassAttribute(інший крайній крайній випадок). В обох випадках виклик dirs()обгортки inspect.getmembers()вирішує це. Однак для стандартних об'єктів цілком достатньо підходу до розуміння списку цього рішення, що фільтрує неактуальні дзвінки. Що деякі піфоністи назвали би це "поганою ідеєю", мене бентежить, але ... я думаю, кожен з них свій?
Сесіль Карі

57

як правило, покладіть __iter__метод у свій клас та повторіть атрибути об'єкта або поставте цей клас mixin у свій клас.

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

Ваш клас:

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

це працює лише тоді, коли oneі twoвизначено після yc = YourClass(). Питання задається питанням про перегляд через існуючу атрис в YourClass(). Крім того, успадкування IterMixinкласу може бути не завжди доступним як рішення.
Сяо

4
vars(yc)дає той самий результат без успадкування від іншого класу.
Натан

1
Мій вище коментар не зовсім правильний; vars(yc)майже те саме, але словник, який ви повертаєте, - це власний екземпляр __dict__, тому його модифікація відобразиться на екземплярі. Це може призвести до проблем, якщо ви не обережні, тому описаний вище метод може бути іноді кращим (хоча скопіювати словник, мабуть, простіше). Також зауважте, що хоча словник, який повертається вище, не є екземпляром __dict__, значення є однаковими, тому зміна будь-яких змінних значень все одно відображатиметься в атрибутах примірника.
Натан

25

Як уже згадувалося в деяких відповідях / коментарях, об’єкти Python вже зберігають словник своїх атрибутів (методи не включені). Це можна отримати як __dict__, але кращим способом є використання vars(хоч вихід однаковий). Зауважте, що зміна цього словника змінить атрибути екземпляра! Це може бути корисно, але також означає, що вам слід бути обережними з використанням цього словника. Ось короткий приклад:

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}

Використання dir(a)дивного, якщо не відвертого поганого підходу до цієї проблеми. Добре, якщо вам дійсно потрібно було перебрати всі атрибути та методи класу (включаючи спеціальні методи на кшталт __init__). Однак, здається, це не те, що ви хочете, і навіть прийнята відповідь іде про це погано, застосовуючи трохи крихкої фільтрації, щоб спробувати видалити методи і залишити лише атрибути; ви можете бачити, як це не вдасться для класу, Aвизначеного вище.

(використання __dict__було зроблено в парі відповідей, але всі вони визначають непотрібні методи замість того, щоб використовувати їх безпосередньо. Лише коментар пропонує використовувати vars).


1
Щось я не з'ясував із підходом vars () - це як впоратися з ситуацією, коли клас A має член, що є ще одним об'єктом, типом якого є визначений користувачем клас B. vars (a), схоже, називає __repr __ () на його член типу B. __repr __ (), наскільки я розумію, повинен повернути рядок. Але коли я називаю vars (a), здається, було б доцільно, щоб цей виклик повернув вкладений дикт, а не дикт зі строковим поданням B.
plafratt

1
Якщо у вас є a.bяк-небудь спеціальний клас, Bто vars(a)["b"] is a.b, як можна було б очікувати; нічого іншого насправді не має сенсу (думати про це a.bяк синтаксичний цукор a.__dict__["b"]). Якщо у вас є d = vars(a)і зателефонуйте, repr(d)він зателефонує repr(d["b"])як частина повернення власного repr-рядка, оскільки тільки клас Bсправді знає, як його слід представляти як рядок.
Натан

2

Об'єкти в python зберігають свої атрибути (включаючи функції) у dict __dict__. Ви можете (але, як правило, не використовувати) це для прямого доступу до атрибутів. Якщо ви просто хочете отримати список, ви також можете зателефонувати dir(obj), який поверне ітерабельний запис із усіма іменами атрибутів, до яких ви могли б перейти getattr.

Однак, потрібно робити що-небудь із назвами змінних, як правило, погана конструкція. Чому б не зберегти їх у колекції?

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

Потім ви можете повторити клавіші за допомогою for key in obj.special_values:


Чи можете ви пояснити трохи більше, як би я використовував колекцію, щоб досягти того, що я хочу?
Пабло Мешер

1
Я не додаю атрибути об’єкту динамічно. Змінні у мене довго залишатимуться однаковими, проте я думаю, було б непогано мати можливість зробити щось на кшталт того, що я маю намір.
Пабло Мешер

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

то просто називайте це ітерабельним

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

Це здається занадто складним. Якщо у мене немає вибору, я б скоріше дотримувався того, що роблю зараз.
Пабло Мешер

0

Правильна відповідь на це - ви не повинні. Якщо ви хочете, щоб цей тип речей або просто використовував диктант, або вам потрібно буде явно додати атрибути до якогось контейнера. Ви можете автоматизувати це, дізнавшись про декораторів.

Зокрема, до речі, method1 у вашому прикладі так само хороший атрибут.


2
Так, я знаю, що не повинен .., але це допомагає мені зрозуміти, як все працює. Я
походжу з фонового режиму

Ви мене переконали. Оновлення методу to_dict () оновлюється набагато простіше, ніж будь-яка відповідь.
Пабло Мешер

1
Чи залишилися люди, які утримаються від догматичних порад. Динамічне програмування спалить вас, якщо ви не будете обережні, але функції є мовою, якою ви користуєтесь.
Генрік Венделбо

0

Для пітона 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
Чи можете ви додати якісь пояснення до своїх публікацій, щоб і непрофесійні пітоністи могли отримати вигоду від ваших відповідей?
not2qubit

-3

Для всіх пітонічних завзятостей, я впевнений, що Йохан Клізе схвалить ваш догматизм;). Я залишаю цю відповідь, продовжую її демерувати. Це насправді робить мене більш довірливою. Залиште коментар, кури!

Для пітона 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

Чи є у вас два відповіді на це питання?
Натан

@Nathan є тому, що один більш багатослівний, а інший більш пітонічний. Я також не знав, що працює швидше, тому вирішив дозволити читачеві вибрати.
Каучукова качка

@nathan ти пітон чи стек переповнення поліції? Існують різні стилі кодування. Я віддаю перевагу непітонічному, тому що я працюю багатьма мовами та технологіями, і думаю, що це, як правило, ідіосинкратично. Тим не менш, розуміння Іїста є крутим і стислим. То чому б не дозволити собі освіченого читача обом?
Каучукова качка

Для всіх пітонічних завзятостей, я впевнений, що Йохан Клізе схвалить ваш догматизм;). Я залишаю цю відповідь, продовжую її демерувати. Це насправді робить мене більш довірливою. Залиште коментар, кури!
Каучукова качка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.