How to make a sequence PREMIUM

Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
7 min. read 4 min. video Python 3.10—3.14
Python Morsels
Watch as video
03:40

How can you make a sequence in Python?

What is a sequence in Python?

Sequences in Python are objects that can be looped over, can be indexed and have a length:

So to make our own sequences, we need to make a class with instances that are iterable, indexable, and work with the built-in len function:

>>> name = "Trey"
>>> for c in name:
...     print(c)
...
T
r
e
y
>>> name[0]
'T'
>>> len(name)
4

Let's make a Fibonacci class which returns the first n numbers in the Fibonacci sequence.

We'll start like this:

class Fibonacci:
    """Python sequence of the first N Fibonacci numbers (default 100)."""

    def __init__(self, n=100):
        self.n = n

Supporting iterability

To make our Fibonacci object sequences, they need to support iterability, so we need a __iter__ method:

class Fibonacci:

    def __init__(self, n=100):
        self.n = n

    def __iter__(self):
        a, b = 0, 1
        for _ in range(self.n):
            yield a
            a, b = b, a + b

Now we can iterate over our Fibonacci objects:

>>> fib = Fibonacci(5)
>>> for n in fib:
...     print(n)
...
0
1
1
2
3

Supporting indexability

They also need to support indexability, which means we need a __getitem__ method:

class Fibonacci:

    ...

    def __getitem__(self, n):
        if n < 0:
            n += self.n
        if n < 0 or n >= self.n:
            raise IndexError("Index out of range")
        # NOTE: this could be more efficient with a closed-form solution
        if n == 0 or n == 1:
              return 1
        return self[n-1] + self[n-2]

Although, once we've implemented a __getitem__ method, we'll actually get iterability for free.

So if we deleted our __iter__ method our objects will still be indexable, but they would also still be iterable:

>>> fib = Fibonacci(10)
>>> fib[3]
3
>>> list(fib)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

But I would actually recommend implementing the __iter__ method even if it might seem redundant. Some Python utilities will actually check whether an object is iterable by looking for the presence of a __iter__ method.

Making our class instances have a length

Lastly, we need a __len__ method in order to make our objects work with the built-in len function:

class Fibonacci:

    def __init__(self, n=100):
        self.n = n

    def __len__(self):
        return self.n

    ...

Now instances of our class can work with the len function:

>>> fib = Fibonacci(10)
>>> len(fib)
10

Making mutable sequences

Mutable sequences need a lot more than immutable ones.

Here's an example of a mutable sequence:

class UniqueList:
    def __init__(self, values):
        self._items = []
        self.extend(values)

    def __len__(self):
        return len(self._items)

    def __getitem__(self, index):
        return self._items[index]

    def __setitem__(self, index, value):
        if value not in self._items:
            self._items[index] = value

    def __delitem__(self, index):
        del self._items[index]

    def append(self, value):
        if value not in self._items:
            self._items.append(value)

    def extend(self, values):
        for value in values:
            self.append(value)

    def pop(self, index=-1):
        value = self[index]
        del self[index]
        return value

    def insert(self, index, value):
        if value not in self._items:
            self._items.insert(index, value)

    def remove(self, value):
        self._items.remove(value)

    def __repr__(self):
        return f"UniqueList({self._items!r})"

This sequence ensures that the items within it are unique:

>>> x = UniqueList([4, 2, 4, 1])
>>> x
UniqueList([4, 2, 1])

Mutable sequences are expected to support all of the features that lists support. So they don't just need item lookups, but also item assignment, as well as item deletion:

>>> x[0] = -1

And they need to support other methods like the append method, for example:

>>> x.append(9)

We implemented all of the functionality necessary for mutable sequences on our own. But we didn't need to! Python has abstract base classes to make this easier.

Reaching for abstract base classes

In Python's collections.abc module, there is an abstract base class called MutableSequence.

If we inherit from this class, it will implement some of this functionality for us for free, as long as we've implemented certain core functionality:

from collections.abc import MutableSequence

class UniqueList(MutableSequence):
    def __init__(self, values):
        self._items = []
        for value in values:
            self.append(value)

    def __len__(self):
        return len(self._items)

    def __getitem__(self, index):
        return self._items[index]

    def __repr__(self):
        return f"UniqueList({self._items!r})"

We haven't implemented all of the core functionality in this case, so when we try to make a new instance of this class we'll see an error:

>>> numbers = UniqueList([1, 4, 2, 3, 4, 7, 2, 6])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class UniqueList with abstract methods __delitem__, __setitem__, insert

Our class is complaining that we haven't implemented __delitem__, __setitem__, and insert methods.

Let's implement those three missing methods:

from collections.abc import MutableSequence

class UniqueList(MutableSequence):
    def __init__(self, values):
        self._items = []
        self.extend(values)

    def __len__(self):
        return len(self._items)

    def __getitem__(self, index):
        return self._items[index]

    def __setitem__(self, index, value):
        if value not in self._items:
            self._items[index] = value

    def __delitem__(self, index):
        del self._items[index]

    def insert(self, index, value):
        if value not in self._items:
            self._items.insert(index, value)

    def __repr__(self):
        return f"UniqueList({self._items!r})"

Now we have a fully functional mutable sequence:

>>> x = UniqueList([4, 2, 4, 1])
>>> x
UniqueList([4, 2, 1])

Our mutable sequence even supports functionality that we didn't implement on our own, like the pop method:

>>> x.pop()
1

This pop method was implemented by the MutableSequence class. That pop method calls our __getitem__ method as well as our __delitem__ method.

Using the Sequence helper class

The collections.abc module also has a Sequence class:

from collections.abc import Sequence

class Fibonacci(Sequence):

    def __init__(self, n=100):
        self.n = n

    def __len__(self):
        return self.n

    def __getitem__(self, n):
        if n < 0:
            n += len(self)
        if n < 0 or n >= len(self):
            raise IndexError("Index out of range")
        # NOTE: this could be more efficient with a closed-form solution
        if n == 0 or n == 1:
              return 1
        return self[n-1] + self[n-2]

The Sequence class is a helper for making sequences that aren't mutable.

It requires us to implement a __len__ method and a __getitem__ method, and it'll automatically make an __iter__ method for us, as well as an index method and a count method:

>>> fib = Fibonacci(10)
>>> fib.index(2)
2
>>> fib.count(2)
1

These two methods are handy for signaling that our objects are fully functional sequences. Pretty much every sequence you'll encounter in Python implements an index method and a count method.

These collections.abc classes give us a bit of functionality for free, but they also help us ensure that our class fully implements a particular interface (a sequence in our case). When inheriting from these collections.abc classes if we forget to implement any of the necessary functionality, our class won't work at all:

>>> from collections.abc import Sequence
>>> class S(Sequence):
...     pass
...
>>> s = S()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class S with abstract methods __getitem__, __len__

These ABC classes ensure that all child classes implement all the methods that they require in order to function.

You can make your own sequence types in Python

Sequences need iterability, indexability, and they need to have a length.

The easiest way to make your own sequences in Python is to inherit from the Sequence class or the MutableSequence class in Python's collections.abc module.

As noted in the documentation, the Sequence class requires implementing __getitem__ and __len__ methods and the "mixin methods" that you'll get for free are __iter__, __reversed__, __contains__, index, and count. The MutableSequence additionally requires implementing __setitem__, __delitem__, and insert methods and the additional "mixin methods" that you'll get for free are append, pop, extend, remove, reverse, and __iadd__.

Python Morsels
Watch as video
03:40
This is a free preview of a premium screencast. You have 1 preview remaining.