Python class accessible by iterator and index
The current accepted answer from @Ignacio Vazquez-Abrams is sufficient. However, others interested in this question may want to consider inheriting their class from an abstract base class (ABC
) (such as those found in the standard module collections.abc
). This does a number of things (there are probably others as well):
- ensures that all of the methods you need to treat your object "like a ____" are there
- it is self-documenting, in that someone reading your code is able to instantly know that you intend your object to "act like a ____".
- allows
isinstance(myobject,SomeABC)
to work correctly. - often provides methods auto-magically so we don't have to define them ourselves
(Note that, in addition to the above, creating your own ABC
can allow you to test for the presence of a specific method or set of methods in any object, and based on this to declare that object to be a subclass of the ABC
, even if the object does not inherit from the ABC
directly. See this answer for more information.)
Example: implement a read-only, list
-like class using ABC
Now as an example, let's choose and implement an ABC
for the class in the original question. There are two requirements:
- the class is iterable
- access the class by index
Obviously, this class is going to be some kind of collection. So what we will do is look at our menu of collection
ABC's to find the appropriate ABC
(note that there are also numeric
ABCs). The appropriate ABC
is dependent upon which abstract methods we wish to use in our class.
We see that an Iterable
is what we are after if we want to use the method __iter__()
, which is what we need in order to do things like for o in myobject:
. However, an Iterable
does not include the method __getitem__()
, which is what we need in order to do things like myobject[i]
. So we'll need to use a different ABC
.
On down the collections.abc
menu of abstract base classes, we see that a Sequence
is the simplest ABC
to offer the functionality we require. And - would you look at that - we get Iterable
functionality as a mixin method - which means we don't have to define it ourselves - for free! We also get __contains__
, __reversed__
, index
, and count
. Which, if you think about it, are all things that should be included in any indexed object. If you had forgotten to include them, users of your code (including, potentially, yourself!) might get pretty annoyed (I know I would).
However, there is a second ABC
that also offers this combination of functionality (iterable, and accessible by []
): a Mapping
. Which one do we want to use?
We recall that the requirement is to be able to access the object by index (like a list
or a tuple
), i.e. not by key (like a dict
). Therefore, we select Sequence
instead of Mapping
.
Sidebar: It's important to note that a Sequence
is read-only (as is a Mapping
), so it will not allow us to do things like myobject[i] = value
, or random.shuffle(myobject)
. If we want to be able do things like that, we need to continue down the menu of ABC
s and use a MutableSequence
(or a MutableMapping
), which will require implementing several additional methods.
Example Code
Now we are able to make our class. We define it, and have it inherit from Sequence
.
from collections.abc import Sequence
class MyClass(Sequence):
pass
If we try to use it, the interpreter will tell us which methods we need to implement before it can be used (note that the methods are also listed on the Python docs page):
>>> myobject = MyClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods __getitem__, __len__
This tells us that if we go ahead and implement __getitem__
and __len__
, we'll be able to use our new class. We might do it like this in Python 3:
from collections.abc import Sequence
class MyClass(Sequence):
def __init__(self,L):
self.L = L
super().__init__()
def __getitem__(self, i):
return self.L[i]
def __len__(self):
return len(self.L)
# Let's test it:
myobject = MyClass([1,2,3])
try:
for idx,_ in enumerate(myobject):
print(myobject[idx])
except Exception:
print("Gah! No good!")
raise
# No Errors!
It works!
Implement both __iter__()
and __getitem__()
et alia methods.