Python: Make class iterable

Question:

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
Asked By: multipleinterfaces

||

Answers:

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

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

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

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: Asclepius

You can make class members iterable within just a single line.

Despite the easy and compact code there are two mayor features included, additionally:

  1. Type checking allows using additional class members not to be iterated.
  2. The technique is also working if (public) class methods are defined. The proposals above using the "__" string checking filtering method propably fail in such cases.
# How to make class members iterable in a single line within Python (O. Simon, 14.4.2022)
# Includes type checking to allow additional class members not to be iterated

class SampleVector():
    
    def __init__(self, x, y, name):
        self.x = x
        self.y = y
        self.name = name 
    
    def __iter__(self):
        return [value for value in self.__dict__.values() if isinstance(value, int) or isinstance(value, float)].__iter__()

if __name__ == '__main__':
    
    v = SampleVector(4, 5, "myVector")
    print (f"The content of sample vector '{v.name}' is:n")
    for m in v:
        print(m)

This solution is fairly close and inspired by answer 12 from Hans Ginzel and Vijay Shanker.

Answered By: O. Simon