Python: сделать класс итерируемым

Я унаследовал проект со многими большими классами, состоящими только из объектов класса (целые числа, строки и т.д.). Я хотел бы иметь возможность проверить наличие атрибута без необходимости определять список атрибутов вручную.

Можно ли сделать python class итерируемым, используя стандартный синтаксис? То есть я хотел бы иметь возможность перебирать все атрибуты класса, используя for attr in Foo: (или даже if attr in Foo) без необходимости сначала создавать экземпляр класса. Я думаю, что могу сделать это, определив __iter__, но до сих пор мне не совсем удалось то, что я ищу.

Я добился кое-чего из того, что хотел, добавив метод __iter__ следующим образом:

class Foo:
    bar = "bar"
    baz = 1
    @staticmethod
    def __iter__():
        return iter([attr for attr in dir(Foo) if attr[:2] != "__"])

Однако это не совсем соответствует тому, что я ищу:

>>> for x in Foo:
...     print(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'classobj' object is not iterable

Тем не менее, это работает:

>>> for x in Foo.__iter__():
...     print(x)
bar
baz

person multipleinterfaces    schedule 25.03.2011    source источник
comment
Если вы хотите проверить наличие атрибута, это можно сделать следующим образом: hasattr . Вам не нужно делать свой класс итерабельным.   -  person user2357112 supports Monica    schedule 24.05.2020


Ответы (4)


Добавьте __iter__ в метакласс вместо самого класса (при условии, что Python 2.x):

class Foo(object):
    bar = "bar"
    baz = 1
    class __metaclass__(type):
        def __iter__(self):
            for attr in dir(self):
                if not attr.startswith("__"):
                    yield attr

Для Python 3.x используйте

class MetaFoo(type):
    def __iter__(self):
        for attr in dir(self):
            if not attr.startswith("__"):
                yield attr

class Foo(metaclass=MetaFoo):
    bar = "bar"
    baz = 1
person Sven Marnach    schedule 25.03.2011
comment
Хороший. Пожалуйста, не могли бы вы объяснить, почему подход ОП не работает? Спасибо. - person NPE; 25.03.2011
comment
@aix: Причина, по которой подход OP не работает, заключается в том, что метод __iter__ работает только для экземпляров класса. Это поднимает метод __iter__ до экземпляров метакласса, то есть класса. - person nmichaels; 25.03.2011
comment
@aix: Как и другие магические методы, __iter__ ищется в пространстве имен типа объекта, а не в пространстве имен самого объекта. Я не нашел объяснения этого в документации по Python, но это легко увидеть в исходный код (найдите определение PyObject_GetIter()). - person Sven Marnach; 25.03.2011
comment
Может ли кто-нибудь объяснить, почему он не работает при написании метода __iter__() как @classmethod в классе Foo? Думаю, ответ @nmichaels не объясняет этого. ` @classmethod def __iter__ (cls): для x в xrange (10): yield x ` - person trudolf; 23.06.2015
comment
@trudolf: Специальные методы ищутся по типу экземпляр, а не сам экземпляр. Если экземпляр является классом, это означает, что специальный метод ищется в метаклассе, а не в самом классе. - person Sven Marnach; 24.06.2015

вот как мы делаем объект класса итерируемым. предоставьте классу метод iter и метод next(), затем вы сможете перебирать атрибуты класса или их значения. вы можете оставить метод next(), если хотите, или вы можете определить next( ) и поднять StopIteration при каком-то условии.

e.g:

class Book(object):
      def __init__(self,title,author):
          self.title = title
          self.author = author

      def __iter__(self):
          for each in self.__dict__.values():
              yield each

>>> book  = Book('The Mill on the Floss','George Eliot')
>>> for each in book: each
...
'George Eliot'
'The Mill on the Floss'

этот класс перебирает значение атрибута класса Book. Объект класса можно сделать итерируемым, также предоставив ему метод getitem. например:

class BenTen(object):
    def __init__(self, bentenlist):
        self.bentenlist = bentenlist
        
    def __getitem__(self,index):
        if index <5:
            return self.bentenlist[index]
        else:
            raise IndexError('this is high enough')

>>> bt_obj = BenTen([x for x in range(15)])
>>>for each in bt_obj:each
...
0
1
2
3
4

теперь, когда объект класса BenTen используется в цикле for-in, getitem вызывается с последовательно более высоким значением индекса, пока он не вызовет IndexError.

person vijay shanker    schedule 01.01.2013
comment
Это перебирает атрибуты экземпляра класса (т.е. book в book = Book(...)); вопрос заключается в непосредственном переборе атрибутов class (т.е. Book в class Book(object):). - person multipleinterfaces; 02.01.2013
comment
Хотя это не ответ на вопрос OP, это помогло мне, потому что я искал это при поиске итерируемого класса. - person dlite922; 22.03.2013

Вы можете перебирать скрытые атрибуты класса с помощью for attr in (elem for elem in dir(Foo) if elem[:2] != '__').

Менее ужасный способ написания:

def class_iter(Class):
    return (elem for elem in dir(Class) if elem[:2] != '__')

тогда

for attr in class_iter(Foo):
    pass
person nmichaels    schedule 25.03.2011
comment
Должен признаться, я предпочитаю это решение, которое выглядит более питоническим, чем решение OP. Но разве это не решило его проблему, я не +1 - person Xavier Combelle; 25.03.2011

Экземпляр enum.Enum может быть итерируемым, и хотя это не общее решение, это разумный вариант для некоторых случаев использования:

from enum import Enum

class Foo(Enum):
    bar = "qux"
    baz = 123

>>> print(*Foo)
Foo.bar Foo.baz

names = [m.name for m in Foo]
>>> print(*names)
bar baz

values = [m.value for m in Foo]
print(*values)
>>> qux 123

Как и в случае с .__dict__, порядок итерации с использованием этого подхода, основанного на Enum, такой же, как и порядок определения.

person Acumenus    schedule 05.05.2018
comment
Это полностью меняет поведение класса и всех его атрибутов. Создание итерируемого класса является незначительным побочным эффектом. Это не решение общей проблемы создания итерируемых классов. - person user2357112 supports Monica; 24.05.2020
comment
@user2357112supportsMonica Во-первых, это странное имя пользователя. Во-вторых, я обновил ответ, упомянув, что этот подход не является общим решением. Тем не менее, это полезно для некоторых случаев использования. - person Acumenus; 24.05.2020