Unravelling subscriptions in Python
For the next post in my syntactic sugar series I want to cover subscriptions. It's quite possible you're not familiar with this formal term, but you are probably familiar with the syntax: the square brackets used for indexing lists and tuples (sequence[4]
), accessing the value of a specified dictionary (dictionary["key"]
), etc. To cover this topic we will break up into three parts: general subscriptions, slicing, and multiple arguments.
General subscriptions
A subscription can get, set, or delete items from a collection. These three operations have equivalent special methods called __getitem__
, __setitem__
, and __delitem__
, respectively. Due to the fact that if a subscription is done on an object that does not have an appropriate special method, we will re-implement the appropriate functions from the operator
module. All three functions take a similar approach, so I will just show how __getitem__
works and let you look at the source code for the other two functions.
The code:
- Gets the type of the container.
- Gets the
__getitem__
method from the type. - If the method doesn't exist, raise
TypeError
. - Otherwise call the method appropriately.
Slicing
The syntax for slicing maps to the slice
class' constructor where any empty value is represented by None
.
::
maps toslice(None, None, None)
1:2:3
maps toslice(1, 2, 3)
1:2
maps toslice(1, 2)
:
maps toslice(None, None)
1:
maps toslice(1, None)
:1
maps toslice(None, 1)
(maps toslice(1)
as well)
The slice object then gets passed into the appropriate special method, so x[1:2:3]
is the equivalent of type(x).__getitem__(x, slice(1, 2, 3))
.
Multiple arguments
If you don't work with the scientific stack and use packages like NumPy, you may not know that you can actually pass in multiple arguments when using the subscription syntax: [1, 2, 3]
. The key difference to a function call, though, is all of the values get bundled up into a tuple that gets passed in as the first argument to the appropriate special method. This translates x[1, 2, 3]
to type(x).__getitem__((1, 2, 3))
. This also means that passing in a tuple with the same values is no different: x[1, 2, 3] == x[(1, 2, 3)]
.