Skip to main content

Special (Magic) Methods: `__str__` and `__repr__`

We've created a Car class that holds data (attributes) and has behaviors (methods). But what happens when we try to print() one of our Car objects directly?

class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year

my_car = Car("Toyota", "Camry", 2023)
print(my_car)

Output:

<__main__.Car object at 0x10e8b6d90>

This output isn't very useful. It's the default string representation for a custom object, telling us the class name and where it's located in memory. To make this more informative, we need to implement some of Python's special methods, also known as "magic methods" or "dunder methods" (because they have double underscores).

This article focuses on two of the most important special methods: __str__ and __repr__.


📚 Prerequisites

You should be comfortable creating a basic class with an __init__ method and instance attributes.


🎯 Article Outline: What You'll Master

In this article, you will learn:

  • What Special Methods Are: Understand that these "dunder" methods let your objects interact with built-in Python operations.
  • __str__(): How to create a user-friendly, readable string representation of your object for end-users.
  • __repr__(): How to create an unambiguous, official string representation of your object for developers.
  • The Key Difference: When to use one versus the other, and how they work together.

🧠 Section 1: __str__ - For a "Pretty" String

The goal of __str__ is to return a string that is readable and user-friendly. It's intended for the end-user of your program.

The print() function and the str() constructor both look for and use the __str__ method if it exists.

Let's add a __str__ method to our Car class.

# str_example.py

class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year

# This is the __str__ special method
def __str__(self) -> str:
"""Returns a user-friendly string representation of the Car."""
return f"A {self.year} {self.make} {self.model}"

my_car = Car("Toyota", "Camry", 2023)

# Now, print() will use our custom __str__ method
print(my_car)

# The str() function also uses it
car_description = str(my_car)
print(car_description)

Output:

A 2023 Toyota Camry
A 2023 Toyota Camry

This is much more useful! We now have full control over how our object is displayed to the user.


💻 Section 2: __repr__ - For a Developer-Focused String

The goal of __repr__ (short for "representation") is to return a string that is unambiguous and, ideally, could be used to recreate the object. It's intended for the developer.

The repr() function calls this method. It's also what you see when you inspect an object in an interactive Python shell. The golden rule for __repr__ is: make it look like a valid Python expression that could create the object.

Let's add a __repr__ method to our Car class.

# repr_example.py

class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year

def __str__(self) -> str:
"""Returns a user-friendly string representation."""
return f"A {self.year} {self.make} {self.model}"

# This is the __repr__ special method
def __repr__(self) -> str:
"""Returns an unambiguous string representation of the Car."""
# Note: This looks like the code you'd use to create the object
return f"Car(make='{self.make}', model='{self.model}', year={self.year})"

my_car = Car("Toyota", "Camry", 2023)

# Let's see the output of repr()
developer_view = repr(my_car)
print(developer_view)

Output:

Car(make='Toyota', model='Camry', year=2023)

This output is perfect for debugging. A developer can see exactly what class the object is and what data was used to create it.


🛠️ Section 3: How They Work Together

So, what's the relationship between the two?

  • When you print() an object, Python looks for a __str__ method. If it finds one, it uses it.
  • If Python cannot find a __str__ method, it will fall back and use the __repr__ method instead.
  • When you inspect an object directly in a shell or call repr(), Python will only look for __repr__.

Because of this fallback behavior, a good rule of thumb is:

Always implement __repr__. Implement __str__ only when you need a "pretty" string representation that is different from the developer representation.

If you only implement one, it should be __repr__, because it will be used for both user-facing and developer-facing contexts.

# repr_fallback_example.py

class Book:
def __init__(self, title):
self.title = title

# This class only has __repr__
def __repr__(self) -> str:
return f"Book(title='{self.title}')"

my_book = Book("Dune")

# Since there is no __str__, print() will use __repr__ as a fallback.
print(my_book)

Output:

Book(title='Dune')

✨ Conclusion & Key Takeaways

Special methods like __str__ and __repr__ allow your custom objects to integrate seamlessly with built-in Python functions and syntax. They give you control over how your objects are represented, which is crucial for creating readable and debuggable code.

Let's summarize the key takeaways:

  • __str__ is for users: It should be readable and provide a nice, informal representation. It's called by print() and str().
  • __repr__ is for developers: It should be unambiguous and look like a valid Python expression that could recreate the object. It's called by repr() and used as a fallback for __str__.
  • Implement __repr__ first: It's the more fundamental of the two and provides a solid default.

Challenge Yourself: Create a Person class with name and age attributes. Implement both a __str__ and a __repr__ method for it. The __str__ should be something friendly like "Alice is 30 years old.", while the __repr__ should be the developer-friendly Person(name='Alice', age=30).


➡️ Next Steps

We've now covered the absolute basics of creating a class. In the next articles, we'll explore more advanced OOP concepts, starting with "Instance, Class, and Static Methods" to understand the different kinds of behaviors you can attach to your classes.

Happy coding!