Nested or Inner Classes in Python

Learn how to declare and use nested classes in python

“There is nothing either good or bad, but thinking makes it so.” ― William Shakespeare, Hamlet

1. Introduction

A nested or inner class is contained within another class. This could be for reasons of encapsulation, where the inner class is not useful by itself.

Here is an example of how the classes are laid out.

class A:
    class B:
        pass
    pass

An inner class in python is a distinct entity in that it does not automatically get access to the outer class members in any special way.

2. Implementing an Iterator without an Inner Class

Let us examine the use of an inner class to implement an iterator. An iterator is an object that can be used in a for-loop for iterating over a collection.

Consider this example of Cats class which stores a bunch of cat names. You can use the add() method to add a cat. Since the class also implements the __iter__() special method to return an iterator, an instance of this class can be used directly in a for-loop.

An iterator is an object that supports the next() method and raises the StopIteration exception when the end of the collection is reached.

class Cats:
    def __init__(self):
        self.cats = []
        self.cur = 0

    def add(self, name):
        self.cats.append(name)
        return self

    def __iter__(self):
        return self

    def next(self):
        i = self.cur
        if i >= len(self.cats):
            raise StopIteration
        self.cur += 1
        return self.cats[i]

Here is how you can use this class.

a = Cats()
a.add('joe').add('jack').add('fink').add('dink')
for c in a:
    print c
# prints
joe
jack
fink
dink

3. Making the Iterator an Inner Class

Now the above implementation has a design fault. It combines the functionalities of the Cats object with an iterator object. Functionally the two entities are separate and this should be reflected in code. Let us do this by moving the iterator functionality into a separate class. And since the iterator class is specialized to the Cats class, it will not be useful outside of the Cats class and we should make it an inner class.

Here is the implementation where the iterator has been separated into the _cat_iter class with the required implementation of the next() method. Also notice that the __iter__() method returns an instance of the iterator class.

class Cats:
    class _cat_iter:
        def __init__(self, cats):
            self.cats = cats
            self.cur = 0

        def next(self):
            i = self.cur
            if i >= len(self.cats):
                raise StopIteration
            self.cur += 1
            return self.cats[i]

    def __init__(self):
        self.cats = []

    def add(self, name):
        self.cats.append(name)
        return self

    def __iter__(self):
        return Cats._cat_iter(self.cats)

Since the interface to the Cats class did not change, there is no change in the client code.

a = Cats()
a.add('joe').add('jack').add('fink').add('dink')
for c in a:
    print c

Conclusion

Python does have support for nested or inner classes. These classes can be used when you do not want to expose the class, and it is closely related to another class.