Sign in to your Python Morsels account to save your screencast settings.
Don't have an account yet? Sign up here.
How can you make 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
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
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.
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
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.
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.
Sequence helper classThe 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.
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__.
We don't learn by reading or watching. We learn by doing. That means writing Python code.
Practice this topic by working on these related Python exercises.
__all__ attribute
Sign in to your Python Morsels account to track your progress.
Don't have an account yet? Sign up here.