Implementing the __repr__ Method
In addition to __str__, Python provides another special method for string representation: __repr__. While __str__ is meant to provide a human-readable representation, __repr__ is intended to provide an unambiguous representation of an object that can be used to recreate the object if possible.
Understanding the __repr__ Method
The __repr__ method is called when you use the repr() function on an object or when you display an object in an interactive session. It should return a string that, when passed to eval(), would create an equivalent object (when possible).
Let's update our Book class to include a __repr__ method:
-
Open book.py in the editor.
-
Update the code to include a __repr__ method:
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self):
return f'"{self.title}" by {self.author} ({self.pages} pages)'
def __repr__(self):
return f'Book("{self.title}", "{self.author}", {self.pages})'
## Create book objects
book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 180)
book2 = Book("To Kill a Mockingbird", "Harper Lee", 281)
## Print the books (uses __str__)
print("String representation (using __str__):")
print(book1)
print(book2)
## Get the representation (uses __repr__)
print("\nRepresentation (using __repr__):")
print(repr(book1))
print(repr(book2))
-
Save the file.
-
Run the script:
python3 book.py
You should see output like:
String representation (using __str__):
"The Great Gatsby" by F. Scott Fitzgerald (180 pages)
"To Kill a Mockingbird" by Harper Lee (281 pages)
Representation (using __repr__):
Book("The Great Gatsby", "F. Scott Fitzgerald", 180)
Book("To Kill a Mockingbird", "Harper Lee", 281)
Differences Between __str__ and __repr__
The main differences between __str__ and __repr__ are:
__str__ is meant for human-readable output, while __repr__ is meant for developers and debugging.
- If
__str__ is not defined but __repr__ is, Python will use __repr__ as a fallback for str() or print().
__repr__ should ideally return a string that can recreate the object when passed to eval(), although this is not always possible or necessary.
The eval() Function with __repr__
When implemented correctly, the string returned by __repr__ can be used with eval() to recreate the object. Let's see this in action:
-
Create a new file called repr_eval.py:
-
Add the following code:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point at ({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"
## Create a point
p1 = Point(3, 4)
## Get the repr string
repr_str = repr(p1)
print(f"Representation: {repr_str}")
## Use eval to recreate the object
p2 = eval(repr_str)
print(f"Recreated object: {p2}")
## Verify they have the same values
print(f"p1.x = {p1.x}, p1.y = {p1.y}")
print(f"p2.x = {p2.x}, p2.y = {p2.y}")
-
Save the file.
-
Run the script:
python3 repr_eval.py
You should see output like:
Representation: Point(3, 4)
Recreated object: Point at (3, 4)
p1.x = 3, p1.y = 4
p2.x = 3, p2.y = 4
This demonstrates that we can recreate the original object using the string returned by __repr__ and the eval() function.
When to Use Each Method
- Use
__init__ to set up the initial state of your objects.
- Use
__str__ to provide a human-readable representation for end-users.
- Use
__repr__ to provide a precise, unambiguous representation for developers and debugging.
Practical Exercise: A Complete Example
Let's put it all together by creating a Rectangle class with all three special methods:
-
Create a new file called rectangle.py:
-
Add the following code:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
def __str__(self):
return f"Rectangle with width {self.width} and height {self.height}"
def __repr__(self):
return f"Rectangle({self.width}, {self.height})"
## Create rectangles
rect1 = Rectangle(5, 10)
rect2 = Rectangle(3, 7)
## Display information about the rectangles
print(f"Rectangle 1: {rect1}")
print(f"Area: {rect1.area()}")
print(f"Perimeter: {rect1.perimeter()}")
print(f"Representation: {repr(rect1)}")
print("\nRectangle 2: {0}".format(rect2))
print(f"Area: {rect2.area()}")
print(f"Perimeter: {rect2.perimeter()}")
print(f"Representation: {repr(rect2)}")
## Recreate a rectangle using eval
rect3 = eval(repr(rect1))
print(f"\nRecreated rectangle: {rect3}")
print(f"Is it the same area? {rect3.area() == rect1.area()}")
-
Save the file.
-
Run the script:
python3 rectangle.py
You should see output like:
Rectangle 1: Rectangle with width 5 and height 10
Area: 50
Perimeter: 30
Representation: Rectangle(5, 10)
Rectangle 2: Rectangle with width 3 and height 7
Area: 21
Perimeter: 20
Representation: Rectangle(3, 7)
Recreated rectangle: Rectangle with width 5 and height 10
Is it the same area? True
This example demonstrates how all three special methods (__init__, __str__, and __repr__) work together to create a well-designed class.