Plugging into Python Syntax: More Special Methods

In What Are Those Underscores?, you learned that some Python syntax, such as + or ==, triggers method calls. For example, when Python sees ’abc’ + ’123’, it turns that into ’abc’.__add__(’123’). When we call print(obj), then obj.__str__() is called to find out what string to print.

You can do this too. All you need to do is define these special methods inside your classes.

The output Python produces when we print a Book isn’t particularly useful:

 >>>​​ ​​python_book​​ ​​=​​ ​​Book(
 ...​​ ​​'Practical Programming'​​,
 ...​​ ​​[​​'Campbell'​​,​​ ​​'Gries'​​,​​ ​​'Montojo'​​],
 ...​​ ​​'Pragmatic Bookshelf'​​,
 ...​​ ​​'978-1-6805026-8-8'​​,
 ...​​ ​​25.0)
 >>>​​ ​​print(python_book)
 <book.Book object at 0x59f410>

This is the default behavior for converting objects to strings: it just shows us where the object is in memory. This is the behavior defined in class object’s method __str__, which our Book class has inherited.

If we want to present a more useful string, we need to explore two more special methods, __str__ and __repr__. __str__ is called when an informal, human-readable version of an object is needed, and __repr__ is called when unambiguous, but possibly less readable, output is desired. In particular, __str__ is called when print is used, and it is also called by function str and by string method format. Method __repr__ is called when you ask for the value of a variable in the Python shell, and it is also called when a collection such as list is printed.

Let’s define method Book.__str__ to provide useful output; this method goes inside class Book, along with __init__ and num_authors:

 def​ __str__(self) -> str:
 """Return a human-readable string representation of this Book.
  """
 
 return​ ​"""Title: {0}
 Authors: {1}
 Publisher: {2}
 ISBN: {3}
 Price: ${4}"""​.format(
  self.title, ​', '​.join(self.authors), self.publisher, self.ISBN, self.price)

Printing a Book now gives more useful information:

 >>>​​ ​​python_book​​ ​​=​​ ​​Book(
 ...​​ ​​'Practical Programming'​​,
 ...​​ ​​[​​'Campbell'​​,​​ ​​'Gries'​​,​​ ​​'Montojo'​​],
 ...​​ ​​'Pragmatic Bookshelf'​​,
 ...​​ ​​'978-1-6805026-8-8'​​,
 ...​​ ​​25.0)
 >>>​​ ​​print(python_book)
 Title: Practical Programming
 Authors: Campbell, Gries, Montojo
 Publisher: Pragmatic Bookshelf
 ISBN: 978-1-6805026-8-8
 Price: $25.0

Method __repr__ is called to get an unambiguous string representation of an object. The string should include the type of the object as well as the values of any instance variables—ideally, if we were to evaluate the string, it would create an object that is equivalent to the one that owns method __repr__. We will show an example of __repr__ in A Case Study: Molecules, Atoms, and PDB Files.

The operator == triggers a call on method __eq__. This method is defined in class object, and so class Book has inherited it; object’s __eq__ produces True exactly when an object is compared to itself. That means that even if two objects contain identical information they will not be considered equal:

 >>>​​ ​​python_book_1​​ ​​=​​ ​​book.Book(
 ...​​ ​​'Practical Programming'​​,
 ...​​ ​​[​​'Campbell'​​,​​ ​​'Gries'​​,​​ ​​'Montojo'​​],
 ...​​ ​​'Pragmatic Bookshelf'​​,
 ...​​ ​​'978-1-6805026-8-8'​​,
 ...​​ ​​25.0)
 >>>​​ ​​python_book_2​​ ​​=​​ ​​book.Book(
 ...​​ ​​'Practical Programming'​​,
 ...​​ ​​[​​'Campbell'​​,​​ ​​'Gries'​​,​​ ​​'Montojo'​​],
 ...​​ ​​'Pragmatic Bookshelf'​​,
 ...​​ ​​'978-1-6805026-8-8'​​,
 ...​​ ​​25.0)
 >>>​​ ​​python_book_1​​ ​​==​​ ​​python_book_2
 False
 >>>​​ ​​python_book_1​​ ​​==​​ ​​python_book_1
 True
 >>>​​ ​​python_book_2​​ ​​==​​ ​​python_book_2
 True

We can override an inherited method by defining a new version in our subclass. This replaces the inherited method so that it is no longer used. As an example, we’ll define method Book.__eq__ to compare two books for equality. Because ISBNs are unique, we can compare using them, but we first need to check whether the object we are comparing to is in fact a Book. We’ll add this method to class Book:

 def​ __eq__(self, other: Any) -> bool:
 """Return True iff other is a book, and this book and other have
  the same ISBN.
 
  >>> python_book = Book( ​​\
  'Practical Programming', ​​\
  ['Campbell', 'Gries', 'Montojo'], ​​\
  'Pragmatic Bookshelf', ​​\
  '978-1-6805026-8-8', ​​\
  25.0)
  >>> python_book_discounted = Book( ​​\
  'Practical Programming', ​​\
  ['Campbell', 'Gries', 'Montojo'], ​​\
  'Pragmatic Bookshelf', ​​\
  '978-1-6805026-8-8', ​​\
  5.0)
  >>> python_book == python_book_discounted
  True
  >>> python_book == ['Not', 'a', 'book']
  False
  """
 
 return​ isinstance(other, Book) ​and​ self.ISBN == other.ISBN

Here is our new method __eq__ in action:

 >>>​​ ​​python_book_1​​ ​​=​​ ​​book.Book(
 ...​​ ​​'Practical Programming'​​,​​ ​​[​​'Campbell'​​,​​ ​​'Gries'​​,​​ ​​'Montojo'​​],
 ...​​ ​​'Pragmatic Bookshelf'​​,​​ ​​'978-1-6805026-8-8'​​,​​ ​​25.0)
 >>>​​ ​​python_book_2​​ ​​=​​ ​​book.Book(
 ...​​ ​​'Practical Programming'​​,​​ ​​[​​'Campbell'​​,​​ ​​'Gries'​​,​​ ​​'Montojo'​​],
 ...​​ ​​'Pragmatic Bookshelf'​​,​​ ​​'978-1-6805026-8-8'​​,​​ ​​25.0)
 >>>​​ ​​survival_book​​ ​​=​​ ​​book.Book(
 ...​​ ​​"New Programmer's Survival Manual"​​,​​ ​​[​​'Carter'​​],
 ...​​ ​​'Pragmatic Bookshelf'​​,​​ ​​'978-1-93435-681-4'​​,​​ ​​19.0)
 >>>​​ ​​python_book_1​​ ​​==​​ ​​python_book_2
 True
 >>>​​ ​​python_book_1​​ ​​==​​ ​​survival_book
 False
 >>>​​ ​​python_book_1​​ ​​==​​ ​​[​​'Not'​​,​​ ​​'a'​​,​​ ​​'book'​​]
 False

Here, then, are the lookup rules for a method call obj.method(...):

  1. Look in the current object’s class. If we find a method with the right name, use it.

  2. If we didn’t find it, look in the superclass. Continue up the class hierarchy until the method is found.

Python has lots of other special methods; the official Python website gives a full list.