And Now for the Conclusion: The Manor's Oak-Panelled Library and __getitem__()[Part 2]
The second in this two-part article on Python's `__getitem__()` special method
This post concludes the two-part article on __getitem__()
. Read the first part here: The Manor House, the Oak-Panelled Library, the Vending Machine, and Python's __getitem__() [Part 1]
That's it. I've had enough of this nonsense. I wasn't sure what bothered me more. Was it the constant humming of the vending machine drowning the centuries-old silence in the library? Or was it the aesthetic aberration of the brightly coloured items in this dull grey fridge piercing the otherwise perfect timeless golden hue of the rest of this room? Probably both.
I stood up, determined to make things right. Then I realised I didn't know how. I stood there for a minute or so until I got my inspiration. A tapestry was hanging on the wall at the other end of the library. I looked around. There was no one around. I pulled the tapestry down. I managed to stay on my feet as I wobbled under its weight.
A minute later, the vending machine had been transformed into a rather quaint, albeit slightly odd, tapestried cabinet.
And yes, I did pull the plug before completing the transformation. Bliss.
Here's the problem. The VendingMachine
class you wrote in Part 1 doesn't make much sense in this story anymore. After all, I saved the day by "resolving" the vending machine issue!
But there's still a whole library to explore. So let's write a new Library
class in Part 2 of this article.
What We Know So Far
In Part 1, we discussed the __getitem__()
special method and how it's linked to the square brackets notation used to fetch items from a data structure. The most common use case is with integers in the square brackets, such as with lists and other sequences. Part 1 also explored the use of two integers in the square brackets, separated by commas:
hideous_machine[2, 1]
This is equivalent to passing the tuple (2, 1)
as an argument to hideous_machine.__getitem__()
.
However, we can use other valid Python expressions in the square brackets as long as the __getitem__()
special method supports the resulting data types.
In this article, you'll write a class that can accept different types of data within the square brackets, and that fetches data accordingly.
Library Catalogue
You'll work on the Library
class, which contains a collection of books. You'll focus on defining the __getitem__()
special method to fetch books from the library. As this article focuses on the __getitem__()
special method, you'll use the square brackets notation on a Library
object. There are three ways we'd like to use the square brackets for this class:
Use an integer as an index. The books are stored sequentially in the library, so you can fetch a book using a number.
Use a string to search for an author name in full or in part.
Use a string to search for a book's name in full or in part.
Let's start writing the code to define the Library
class. You'll also need a simple Book
class to deal with the books:
The Book
class has three data attributes, .title
, .author
, and .publication_year
. You can expand this class to make it more useful, such as by dealing with checking a book out of the library, say. But this will do just fine for what we need for this article. You could even use a dataclass or a named tuple for this, but I'll use a standard class for clarity.
The main class for this article is Library
. It has a .books
data attribute that's a list. The method add_books()
adds Book
objects to the Library
instance. You can make this method more versatile if you wish by allowing it to also accept a single Book
instance as an argument. But I'll keep this method as it is in this article.
Let's create an instance of Library
and add several Book
instances to it:
You create nine Book
instances and a Library
instance called gorgeous_library
. The name is apt since the hideous vending machine is no longer there! You add the books to the library using add_books()
.
This code should show the nine books, one on each line, since you're unpacking gorgeous_library.books
and setting the separator to "\n"
in the print()
function:
<__main__.Book object at 0x7f97d01e4c70>
<__main__.Book object at 0x7f97d01e4c10>
<__main__.Book object at 0x7f97d01e4b20>
<__main__.Book object at 0x7f97d01e4a30>
<__main__.Book object at 0x7f97d01e49d0>
<__main__.Book object at 0x7f97d01e4970>
<__main__.Book object at 0x7f97d01e4910>
<__main__.Book object at 0x7f97d01e48b0>
<__main__.Book object at 0x7f97d01e4850>
But it does not. You need to add a __str__()
special method to the Book
class:
The output now shows the books in the library:
To Kill a Mockingbird by Harper Lee (1960)
1984 by George Orwell (1949)
Animal Farm by George Orwell (1945)
The Hitchhiker's Guide to the Galaxy by Douglas Adams (1979)
Harry Potter and the Philosopher's Stone by J.K. Rowling (1997)
Harry Potter and the Chamber of Secrets by J.K. Rowling (1998)
A Brief History of Time by Stephen Hawking (1988)
The Selfish Gene by Richard Dawkins (1976)
Cosmos by Carl Sagan (1980)
Next, we'll focus on crafting a __getitem__()
special method to meet all the requirements.
Fetching a Book by Position
Let's add a __getitem__()
method to Library
:
The parameter item
in __getitem__()
is used to index the list self.books
. The output shows the fourth book in the library since you use the index 3
:
The Hitchhiker's Guide to the Galaxy by Douglas Adams (1979)
I'm trying to think how to make a passing reference to the number 42, but I can't think of a good one!
Fetching a Book by Author or Title Search
That was the easy bit. Next, we'd like to use the square brackets notation to search by an author's name. For example, gorgeous_library["Orwell"]
should return a list of all of George Orwell's books in the library.
The __getitem__()
method must distinguish between the different types of input. You can use isinstance()
for this:
The string "Orwell"
you use in the square brackets is passed to the parameter item
in __getitem__()
. You use isinstance()
to identify this as a string. You can also raise a TypeError
if the data type is neither an integer nor a string, but I won't show this in the code.
The list comprehension looks for all books for which the .author
data attribute contains the search string. The parameter item
contains the search string. The string method casefold()
enables you to make comparisons irrespective of the search string's case. Here's the output from this code (shown on separate lines for clarity):
[
<__main__.Book object at 0x7fa27f9e4f70>,
<__main__.Book object at 0x7fa27f9e4e50>,
]
The Book
class has a __str__()
method. But in this case, it's the "official" string representation we need to use, which is returned by the __repr__()
special method. You can read more about the differences between the two string representations in this article.
Let's add __repr__()
to the Book
class:
The output from the code now shows a list with the string representation from Book.__repr__()
:
[
Book('1984', 'George Orwell', 1949),
Book('Animal Farm', 'George Orwell', 1945),
]
Or, if you prefer, you can unpack the results and use "\n"
as a separator in the print()
function:
print(*gorgeous_library["Orwell"], sep="\n")
This prints the books separately rather than in a list. The string representation from __str__()
is used in this case:
1984 by George Orwell (1949)
Animal Farm by George Orwell (1945)
Let's add the 'search by title' feature to __getitem__()
:
The new list comprehension is similar to the previous one but compares the string to book.title
instead of book.author
. The two lists, matching_author
and matching_title
, are added together and returned.
On the last line, you search for all books with "Potter"
in the title. The output is the following:
Harry Potter and the Philosopher's Stone by J.K. Rowling (1997)
Harry Potter and the Chamber of Secrets by J.K. Rowling (1998)
When you use a string in the square brackets, the __getitem__()
method searches both by title and by author and returns all matches. Let's confirm this by adding one more book to the library. This code snippet, which is the last in this article, shows the code in full:
The last book in books_data
is by Beatrix Potter. Therefore, when you search for "Potter"
, the output includes both the Harry Potter books since they include "Potter"
in the title, and the Beatrix Potter book since "Potter"
is the author's surname:
The Tale of Peter Rabbit by Beatrix Potter (1902)
Harry Potter and the Philosopher's Stone by J.K. Rowling (1997)
Harry Potter and the Chamber of Secrets by J.K. Rowling (1998)
You can now use either an integer or a string within the square brackets with a Library
object. When you use an integer, you fetch a book using its position in the data structure. When you use a string, you fetch all books that contain that string either in the author field or in the title.
Final Words
There's nothing magic about fetching items from a data structure using the square brackets notation. The mystery disappears once you figure out that a method does all the hard work!
And as for the weekend away in the English countryside? Once the vending machine was unplugged and covered, I managed to get lots of reading done in the library.
Code in this article uses Python 3.11
Stop Stack
#17
Recently published articles on The Python Coding Stack:
The Anatomy of a for Loop. What happens behind the scenes when you run a Python
for
loop? How complex can it be?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"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
Recently published articles on Breaking the Rules, my other substack about narrative technical writing:
Once Upon an Article (Pilot Episode) …because Once Upon a Technical Article didn't sound right. What's this Substack about?
Are You Ready to Break the Rules? Narrative Technical Writing: Using storytelling techniques in technical articles
The Different Flavours of Narrative Technical Writing. Why I'm using more storytelling techniques in my Python articles
Stats on the Stack
Age: 2 months, 2 weeks, and 2 days old
Number of articles: 17
Subscribers: 611
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.