Python: Make class iterable

29,676

Solution 1

Add the __iter__ to the metaclass instead of the class itself (assuming 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

For Python 3.x, use

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

Solution 2

this is how we make a class object iterable. provide the class with a iter and a next() method, then you can iterate over class attributes or their values.you can leave the next() method if you want to, or you can define next() and raise StopIteration on some condition.

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'

this class iterates over attribute value of class Book. A class object can be made iterable by providing it with a getitem method too. e.g:

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

now when the object of BenTen class is used in a for-in loop, getitem is called with succesively higher index value, till it raises IndexError.

Solution 3

You can iterate over the class's unhidden attributes with for attr in (elem for elem in dir(Foo) if elem[:2] != '__').

A less horrible way to spell that is:

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

then

for attr in class_iter(Foo):
    pass
Share:
29,676

Related videos on Youtube

multipleinterfaces
Author by

multipleinterfaces

This space is reserved for future use. It must be NULL.

Updated on April 15, 2022

Comments

  • multipleinterfaces
    multipleinterfaces almost 2 years

    I have inherited a project with many large classes constituent of nothing but class objects (integers, strings, etc). I'd like to be able to check if an attribute is present without needed to define a list of attributes manually.

    Is it possible to make a python class iterable itself using the standard syntax? That is, I'd like to be able to iterate over all of a class's attributes using for attr in Foo: (or even if attr in Foo) without needing to create an instance of the class first. I think I can do this by defining __iter__, but so far I haven't quite managed what I'm looking for.

    I've achieved some of what I want by adding an __iter__ method like so:

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

    However, this does not quite accomplish what I'm looking for:

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

    Even so, this works:

    >>> for x in Foo.__iter__():
    ...     print(x)
    bar
    baz
    
    • user2357112
      user2357112 almost 4 years
      If you want to check whether an attribute is present, the way to do that is hasattr. You don't need to make your class iterable.
  • NPE
    NPE about 13 years
    Nice. Please could you explain why the OP's approach doesn't work? Thanks.
  • Xavier Combelle
    Xavier Combelle about 13 years
    I must admit I prefer this solution which looks more pythonic than the one of OP. But has it's not solve his problem I did not +1
  • nmichaels
    nmichaels about 13 years
    @aix: The reason OP's approach doesn't work is that the __iter__ method only works for instances of the class. This bumps the __iter__ method up to instances of the metaclass, i.e. the class.
  • Sven Marnach
    Sven Marnach about 13 years
    @aix: As other magic methods, __iter__ is looked up in the name space of the type of the object rather than in the object's name space itself. I did not really find this explained in the Python docs, but it can be easily seen in the source code (search for the definition of PyObject_GetIter()).
  • multipleinterfaces
    multipleinterfaces about 11 years
    This iterates over the attributes of an instance of a class (i.e. the book in book = Book(...)); the question is about iterating over the class attributes directly (i.e. the Book in class Book(object):).
  • dlite922
    dlite922 about 11 years
    Although this is not the answer to OP's question, it helped me because I was looking for this when searching for iterable class.
  • trudolf
    trudolf almost 9 years
    Could someone explain why it doesn't work, when writing the __iter__() method as a @classmethod on the Foo class? @nmichaels answer doesn't explain this I think. ` @classmethod def __iter__(cls): for x in xrange(10): yield x `
  • Sven Marnach
    Sven Marnach almost 9 years
    @trudolf: Special methods are looked up on the type of the instance, not on the instance itself. If the instance is a class, this means the special method is looked up on the metaclass, not the class itself.
  • user2357112
    user2357112 almost 4 years
    This completely changes the behavior of the class and all its attributes. Making the class iterable is a minor side effect. It's not a solution to the general problem of making classes iterable.
  • Asclepius
    Asclepius almost 4 years
    @user2357112supportsMonica First, that's a weird username. Second, I have now updated the answer to mention that this approach is not a general solution. It is nevertheless useful for some use cases.
  • K. Symbol
    K. Symbol about 2 years
    It should be __iter__() and __next__()?