Python: Make class iterable

Posted on

Question :

Python: Make class iterable

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

Answer #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
Answered By: Sven Marnach

Answer #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.

Answered By: vijay shanker

Answer #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
Answered By: nmichaels

Answer #4:

class MetaItetaror(type):
    def __iter__(cls):
        return iter(
            filter(
                lambda k: not k[0].startswith('__'),
                cls.__dict__.iteritems()
            )
        )


class Klass:
    __metaclass__ = MetaItetaror

    iterable_attr_names = {'x', 'y', 'z'}
    x = 5
    y = 6
    z = 7


for v in Klass:
    print v
Answered By: sanit

Answer #5:

An instance of enum.Enum happens to be iterable, and while it is not a general solution, it is a reasonable option for some use cases:

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

As with .__dict__, the order of iteration using this Enum based approach is the same as the order of definition.

Answered By: Acumenus

Leave a Reply

Your email address will not be published. Required fields are marked *