Python make class iterable – Python : How to make a class Iterable & create Iterator Class for it ?

How to make a class Iterable & create Iterator Class for it ?

Python make class iterable: We are going to see how we can make a class iterable and also creating an iterator class for it.

What’s the need to make a Custom class Iterable ?

User defined classes are not iterable by default. To make the class objects iterable we have to make the class iterable and also create an iterator class for it.

Let’s try to iterate the class with a for loop

class School:
    """
    Contains List of Junior and senior school students
    """

    def __init__(self):
        self._juniorStudents = list()
        self._seniorStudents = list()

    def addJuniorStudents(self, members):
        self._juniorStudents += members

    def addSeniorStudents(self, members):
        self._seniorStudents += members


def main():
    # Create school class object
    school = School()
    # Add name of junior school students
    school.addJuniorStudents(["Seema", "Jenny", "Morris"
])
    # Add name of senior school students
    school.addSeniorStudents(["Ritika", "Ronnie", "Aaditya"
])
    iter(school)
        for member in school:
        print(member)
# The iter() will throw an error saying school is not iterable

The above code will throw an error as the School class is not iterable yet.

How to make your Custom Class Iterable | The Iterator Protocol :

In order to make the class iterable, we need to override the iter( ) function inside the class so that the function returns the object of the iterator class which is associated with the iterable class.

class SchoolIterator:
    """Iterator class"""

    def __init__(self, school):
        # School object reference
        self._school = school
        # index variable to keep track
        self._index = 0

    def __next__(self):
        """'Returns the next value from school object's lists"""
        if self._index < (
            len(self._school._juniorStudents) + len(self._school._seniorStudents)
        ):
            if self._index < len(
                self._school._juniorStudents
            ):  # Check if junior student members are fully iterated or not
                result = (self._school._juniorStudents[self._index], "junior")
            else:
                result = (
                    self._school._seniorStudents[
                        self._index - len(self._school._juniorStudents)
                    ],
                    "senior",
                )
            self._index += 1
            return result
        # Iteration ends
        raise StopIteration


class School:
    """
    Contains List of Junior and senior school students
    """

    def __init__(self):
        self._juniorStudents = list()
        self._seniorStudents = list()

    def addJuniorStudents(self, members):
        self._juniorStudents += members

    def addSeniorStudents(self, members):
        self._seniorStudents += members

    def __iter__(self):
        """Returns Iterator object"""
        return SchoolIterator(self)


def main():
    # Create school class object
    school = School()
    # Add name of junior school students
    school.addJuniorStudents(["Seema", "Jenny", "Morris"
])
    # Add name of senior school students
    school.addSeniorStudents(["Ritika", "Ronnie", "Aaditya"
])
    iterator = iter(school)
    print(iterator)

main()
Output :
<__main__.SchoolIterator object at 0x01BC6B10>

The __iter__( ) has been overridden in the School class which now returns the object from the schoolIterator class. And when we call iter( ) function on the school class it will call __iter__( ) function on the object.

How to create an Iterator Class :

In order to create an iterator class, we have to override the __next__( ) function so that every time we call a function, it should return the next iterable class until there are no elements. If there are no next elements, then it should raise the StopIteration.

After that we need to make the class object return the next element from the School class Object’s data member

CODE:

class SchoolIterator:
    """Iterator class"""

    def __init__(self, school):
        # School object reference
        self._school = school
        # index variable to keep track
        self._index = 0

    def __next__(self):
        """'Returns the next value from school object's lists"""
        if self._index < (
            len(self._school._juniorStudents) + len(self._school._seniorStudents)
        ):
            if self._index < len(
                self._school._juniorStudents
            ):  # Check if junior student members are fully iterated or not
                result = (self._school._juniorStudents[self._index], "junior")
            else:
                result = (
                    self._school._seniorStudents[
                        self._index - len(self._school._juniorStudents)
                    ],
                    "senior",
                )
            self._index += 1
            return result
        # Iteration ends
        raise StopIteration


class School:
    """
    Contains List of Junior and senior school students
    """

    def __init__(self):
        self._juniorStudents = list()
        self._seniorStudents = list()

    def addJuniorStudents(self, members):
        self._juniorStudents += members

    def addSeniorStudents(self, members):
        self._seniorStudents += members

    def __iter__(self):
        """Returns Iterator object"""
        return SchoolIterator(self)


def main():
    # Create school class object
    school = School()
    # Add name of junior school students
    school.addJuniorStudents(["Seema", "Jenny", "Morris"])
    # Add name of senior school students
    school.addSeniorStudents(["Ritika", "Ronnie", "Aaditya"])
    iterator = iter(school)
    print(iterator)
    while True:
        try:
            # Get next element from SchoolIterator object using iterator object
            elem = next(iterator)
            # Print the element
            print(elem)
        except StopIteration:
            break


main()
Output :
<__main__.SchoolIterator object at 0x01A96B10>
('Seema', 'junior')
('Jenny', 'junior')
('Morris', 'junior')
('Ritika', 'senior')
('Ronnie', 'senior')
('Aaditya', 'senior')

The Working

The iter( ) function calls the overridden __iter__( ) function on the school objects, which would return, the SchoolIterator object. Upon calling the next( ) function, it would call our overridden function  __next__( ) internally. The _index variable is being used here to keep track of the iterated elements. So every time we call the function it iterates the objects and in the need it raises the StopIteration.

class SchoolIterator:
    """Iterator class"""

    def __init__(self, school):
        # School object reference
        self._school = school
        # index variable to keep track
        self._index = 0

    def __next__(self):
        """'Returns the next value from school object's lists"""
        if self._index < (
            len(self._school._juniorStudents) + len(self._school._seniorStudents)
        ):
            if self._index < len(
                self._school._juniorStudents
            ):  # Check if junior student members are fully iterated or not
                result = (self._school._juniorStudents[self._index], "junior")
            else:
                result = (
                    self._school._seniorStudents[
                        self._index - len(self._school._juniorStudents)
                    ],
                    "senior",
                )
            self._index += 1
            return result
        # Iteration ends
        raise StopIteration


class School:
    """
    Contains List of Junior and senior school students
    """

    def __init__(self):
        self._juniorStudents = list()
        self._seniorStudents = list()

    def addJuniorStudents(self, members):
        self._juniorStudents += members

    def addSeniorStudents(self, members):
        self._seniorStudents += members

    def __iter__(self):
        """Returns Iterator object"""
        return SchoolIterator(self)


def main():
    # Create school class object
    school = School()
    # Add name of junior school students
    school.addJuniorStudents(["Seema", "Jenny", "Morris"])
    # Add name of senior school students
    school.addSeniorStudents(["Ritika", "Ronnie", "Aaditya"])
    iterator = iter(school)
#Using for loop
    print(iterator)
    for member in school:
        print(member)


main()
Output :

<__main__.SchoolIterator object at 0x7fefbe6f34f0>
('Seema', 'junior')
('Jenny', 'junior')
('Morris', 'junior')
('Ritika', 'senior')
('Ronnie', 'senior')
('Aaditya', 'senior')
>