Methods whose names begin and end with two underscores have special predefined meanings in Python
In , 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 you 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 you 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 0x7f749d11acf0> |
What you see is the result of the default conversion of objects to strings: it just shows you where the object is in memory. This behavior is defined in the class object’s method __str__, which your Book class has inherited.
If you want to present a more useful string, you 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 informative 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 f"""Title: {self.title} |
| | Authors: {', '.join(self.authors)} |
| | Publisher: {self.publisher} |
| | ISBN: {self.ISBN} |
| | Price: ${self.price}""" |
Printing a Book now gives more helpful 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 |
Another dunder 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 you were to evaluate the string, it would create an object equivalent to the one that owns the method __repr__. You will see an example of __repr__ in .
The operator == triggers a call to the method __eq__. This method is defined in the class object, and so the 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 |
You can override an inherited method by defining a new version in your subclass. Overriding replaces the inherited method so that it is no longer used. As an example, let’s define method Book.__eq__ to compare two books for equality. Because ISBNs are unique, you can compare books using them; however, you first need to verify whether the object you are comparing to is indeed a Book. Let’s add this method to the 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 the 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(...):
Look in the current object’s class. If you find a method with the correct name, use it.
If you didn’t find it, look in the superclass. Continue up the class hierarchy until the method is found.
Python has many other special methods; the official Python website provides a comprehensive list.