The Manor House, the Oak-Panelled Library, the Vending Machine, and Python's __getitem__() [Part 1]
Understanding how to use the Python special method __getitem__(). The first of a two-part article
We spent a long weekend in an English countryside manor house last month. After dropping my bags in the room, I went to explore. I spotted the sign saying "Library", and of course, I followed the arrow. As I stepped into the wood-panelled room, I could feel the sense of timeless serenity and tranquillity.
Light filtered in through glass windows, rows upon rows of built-in oak bookshelves stretched from floor to ceiling, filled to the brim with venerable volumes. Their bindings showed a spectrum of colours faded into pastels, the gold lettering on the spines catching the light. This was the perfect spot for a weekend of reading. I looked around to find my "perfect" spot. Then I saw it—no, not the sought-out idea armchair; I spotted something else.
There was a hideous vending machine selling oily crisps and sugary drinks fitted into one of the bookcases. Why?! But why?
I'll let you in on a secret. This monstrosity doesn't exist. Trust me, if it did, I would have run out of the manor and never looked back. But, if you can live with the revulsion, let's stay in this library a bit longer. It will help us explore Python's __getitem__()
special method.
I'll assume you're familiar with the basics of object-oriented programming (OOP). But if you need to read more, you can read Chapter 7 | Object-Oriented Programming in The Python Coding Book or the Harry Potter-themed series of OOP articles here on The Python Coding Stack.
• Author’s note: I’ve recently started a new Substack which focusses on my thoughts and ideas on technical writing, and my experiments with using storytelling techniques when writing about Python. If you’re reading this, you’re likely interested in Python and you may not be interested in the process of technical writing itself. Fair enough. But if you happen to also be interested in narrative technical writing, you can subscribe to
•Vending Machine • First Attempt
Let's create a basic class to represent a vending machine:
The VendingMachine
class has an __init__()
method, which creates the data attribute .items_on_sale
and the method add_items()
. The items for sale in the vending machine are stored in a list. For now, let's assume for simplicity that the slots in the vending machine are numbered sequentially, starting from zero. Therefore, the item's number in the machine matches its list index.
You can stock the vending machine with a few items. I'll replace code that's unchanged from the previous code blocks in this article with # ...
for brevity and clarity:
You could write a method to access an item from the vending machine. But what if you'd like to use the square bracket notation [ ]
to fetch a value from this VendingMachine
object in the same way you would with built-in types such as lists and tuples?
Let's try it out to see what happens:
Unfortunately, this won't work. You get a TypeError
:
TypeError: 'VendingMachine' object is not subscriptable
You can't subscript this object. This means you can't use square brackets [ ]
to fetch a value.
But there's nothing magical about using square brackets like the ones to fetch items from lists or tuples. There is a special method associated with this notation that we'll explore in the next section.
Defining __getitem__()
Let's define the __getitem__()
special method. This is one of the methods that have double underscores at the beginning and end of the method name. As the name implies, these methods are special:
The __getitem__()
special method accepts two parameters:
self
: the first parameter used by convention in all instance methods. This refers to the instance itself.idx
: the number of the slot in the vending machine. This matches the index in the listself.items_on_sale
.
This special method enables you to use the square brackets notation [ ]
to refer to an item within the VendingMachine
instance. The value you put in the square brackets is passed to the parameter named idx
. The __getitem__()
method returns the value required.
Let's try running the code now. I'm showing the code as it stands at the moment. There are no new additions in the following code block.:
The output from this code is the following:
Still Water
Now you can access an item from the vending machine using the square brackets notation. You use the index 2
, which returns "Still Water"
, the third item in the vending machine.
Let's look at the following expression:
hideous_machine[2]
This expression is equivalent to the following one:
hideous_machine.__getitem__(2)
Therefore, when you use the square brackets notation, the program calls a special method. The expression returns the value returned by __getitem__()
.
Let's finish off this section with one more addition to __getitem__()
:
You raise a TypeError
if the value passed to the special method, which is the same value in the square brackets, is not an integer. You could also handle the case when the index is out of bounds and raise an IndexError
. But in this case, I'll leave this out of the method and let the list handle this scenario.
Note that using the square brackets, and therefore __getitem__()
, does not remove the item from the vending machine. You need another method to remove an item when someone buys it. You'll deal with this in a later version of the VendingMachine
class.
Vending Machine • Second Attempt • Multiple Items in Each Slot
Let's account for the possibility of having multiple items of the same product in one of the vending machine slots. You can create a new Item
class to manage the items and the quantities in the vending machine:
The class Item
has two data attributes to store the name and quantity of the item. You also include the __str__()
special method to create a user-friendly string representation of the object. You'll see the effect of this special method shortly.
In the VendingMachine
class, the method add_items()
hasn't changed. But now, there's the expectation that the argument you pass to the parameter items
is a sequence of objects of type Item
.
Therefore, the data attribute .items_on_sale
now contains Item
objects. You can test the class as before, but now you need a list of Item
objects instead of strings:
This code gives the following output:
Still Water - 7 left
When you print the Item
object, the string representation shown is the one returned by the special method Item.__str__()
.
You're ready to add the buy_item()
method to purchase an item from the vending machine:
Let's look at the first line in buy_item()
:
item = self[idx]
The name self
refers to the VendingMachine
instance. Since the class has a __getitem__()
special method, you can use the square brackets notation. If the quantity is not zero, the method reduces the quantity of the item and returns the item's name. Otherwise, it prints a warning and returns None
.
The last three lines of the code display the item in the third slot before and after purchasing one of these items. This gives the following output:
Still Water - 7 left
Still Water - 6 left
The second printout shows that there's one fewer item available since one was purchased.
You can also check what happens when you empty the slot by purchasing all the items in that slot:
You repeat the call to buy_item(2)
eight times. This empties the slot since there are seven items in the slot at the beginning. The program warns you when you try to purchase an item for the eighth time since the slot is empty. Here's the output from this code:
Still Water - 7 left
Still Water - 6 left
Still Water - 5 left
Still Water - 4 left
Still Water - 3 left
Still Water - 2 left
Still Water - 1 left
Still Water - 0 left
This slot is empty
This works. But, many vending machines require you to tap in the row and the column of the product you want rather than just a single number. Also, there can be empty slots in a vending machine. The index-based approach in the current version of VendingMachine
doesn't deal with these issues. Let's look at a third option in the next section.
Vending Machine • Third Attempt • Choose Row and Column
The items in the vending machine were stored in the list .items_on_sale
in the previous version. This is what the list looks like when it has items in it:
hideous_machine.items_on_sale = [Item(...), Item(...), ..., Item(...)]
It's a list of Item
objects. I'm using ellipsis ...
to represent additional data not shown in the line above. The method add_items()
adds the items to the list, so you never need to write a line like the one above. I'm showing it so we can picture what the data looks like.
Let's replace this with a dictionary that has the following format:
hideous_machine.items_on_sale = {
 (1, 1): Item(...),
 (1, 3): Item(...),
 (2, 1): Item(...),
  ...
}
The data attribute .items_on_sale
is now a dictionary. You use a tuple of integers as keys in this dictionary. The integers within each tuple represent the row and column of the slot in the vending machine. The value associated with each key is an Item()
object.
Normally, such vending machines use a letter to represent the row and a number for the column. However, I'll use numbers for both in this example.
Let's change the class definition to make this data attribute a dictionary instead of a list:
But we still would like to use the square brackets notation [ ]
to access an item. However, we can no longer use a single index as we do in lists or in the previous version of VendingMachine
. We want to be able to write hideous_machine[2, 1]
, say, to fetch the item located in row 2 and column 1. We no longer need to use zero-indexing in this case.
Let's look at an aside briefly to see what happens to the values we put in the square brackets for an object that has a __getitem__()
method:
This short example has a class with a __getitem__()
method and nothing else. You print the value of the parameter position
. Next, you create an instance of this class and use the square brackets on that instance. But you include two numbers in the square brackets instead of one:
my_test[3, 5]
This calls the object's __getitem__()
method and prints any arguments passed to this method. You can see that this prints the tuple (3, 5)
with the two values you placed within the square brackets.
Therefore, we can use more than one value within the square brackets. Let's use this knowledge to update the VendingMachine
class's __getitem___()
special method:
So, if you have a VendingMachine
instance called hideous_machine
, as you did earlier, you can write hideous_machine[2, 1]
. The tuple (2, 1)
is passed to the __getitem__()
special method. It's passed to the parameter position
. Then, you unpack this tuple into two separate variables, row
and column
. You use these values to fetch items from the dictionary self.items_on_sale
.
Since we're no longer placing items sequentially in the next free slot available, as in the previous example, it may be best to replace add_items()
with add_item()
. You'll add one item at a time and provide the location within the vending machine to place this item:
I've chosen a simple version of this method which doesn't check whether the slot is empty. You can add these checks to your version if you wish.
Finally, we can update buy_item()
:
The parameter self
refers to the VendingMachine
instance. Since this object has a __getitem__()
method that allows you to use two integers within the square brackets, you can use self[row, column]
to fetch the Item
object stored at that location.
Let's try this out:
This code outputs the following result:
Still Water - 7 left
Still Water - 6 left
With these changes, you can access an item from the VendingMachine
object using the square brackets notation, with both the row and the column included in the square brackets.
Let's finish this article with the full code showing the final version of the VendingMachine
class:
This is Unbearable
I'm sitting in the library. It's late in the afternoon. The sun is low in the sky, and the room is glowing. I placed myself strategically in the library so that the hideous vending machine was behind me—therefore, out of sight.
But it doesn't work. I know it's there. Once I've seen this monstrosity in this otherwise perfect room, I can't unsee it.
That's it. I've had enough…
Make sure you don't miss Part 2 of this story, which will be out in a few days, to find the conclusion of this drama… and to read a bit more about Python's __getitem__()
special method.
Code in this article uses Python 3.11
Stop Stack
#15
This week, I started a new Substack:
. It's related to this one, but also quite different. In the new Substack, I'll journal my evolving thoughts and ideas about narrative technical writing. This is the approach I've been experimenting with over the past years to include techniques from storytelling into my technical writing. Of course, it's normal service here on The Python Coding Stack. The articles on The Python Coding Stack are the result of my reading, thinking, and experimentation on the topics of storytelling and narrative technical writing.Here's the first article on
, which sets the scene for the new Substack: Are You Ready to Break the Rules?I've also written a guest article for
which, in many ways, launches my writing about storytelling and narrative technical writing. Here it is: The Different Flavours of Narrative Technical Writing
Recently published articles on The Python Coding Stack:
"You Have Your Mother's Eyes" • Inheritance in Python Classes. Year 5 at Hogwarts School of Codecraft and Algorithmancy • Inheritance
Casting A Spell • More Interaction Between Classes. Year 4 at Hogwarts School of Codecraft and Algorithmancy • More on Methods and Classes
An Object That Contains Objects • Python's Containers. Containers • Part 4 of the Data Structure Categories Series
Zen and The Art of Python
turtle
Animations • A Step-by-Step Guide. Python'sturtle
is not just for drawing simple shapesThe One About The Taxi Driver, Mappings, and Sequences • A Short Trip to 42 Python Street. How can London cabbies help us learn about mappings and sequences in Python?
Stats on the Stack — I'll start adding a new section in Stop Stack to share some key stats about The Python Coding Stack
Age: 2 months, 1 week, and 2 days old
Number of articles: 15
Subscribers: 549
There's a new page on The Python Coding Stack's homepage called The Python Series—that's series as a plural noun! It's a shame series is a word that has the same singular and plural form. For the avid readers, you know that I publish several stand-alone articles, but I also publish articles that are part of series (pl.) I'll use this page to keep all series in one place. At the moment, there are links to The Data Structure Category Series and The Harry Potter Object-Oriented Programming Series. I'll add more links to future series here, too.
Most articles will be published in full on the free subscription. However, a lot of effort and time goes into crafting and preparing these articles. If you enjoy the content and find it useful, and if you're in a position to do so, you can become a paid subscriber. In addition to supporting this work, you'll get access to the full archive of articles and some paid-only articles.