Execute Program

Python in Detail: Customizing Str and Repr

Welcome to the Customizing Str and Repr lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • What happens when we call repr or str on a custom class? By default, they return the same thing.

  • >
    class Point:
    def __init__(self, x, y):
    self.x = x
    self.y = y
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    str(Point(3, 5))
    Result:
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    repr(Point(3, 5))
    Result:
  • These results include the class name and the objects' memory addresses. That lets us tell different objects apart, since they're stored at different memory addresses. But we often want more information than this. For example, these repr values don't tell us the point's .x and .y coordinates.

  • Fortunately, we can customize str and repr via the .__str__ and .__repr__ dunder methods.

  • >
    class Point:
    def __init__(self, x, y):
    self.x = x
    self.y = y

    def __repr__(self):
    return "Point(" + repr(self.x) + ", " + repr(self.y) + ")"
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    center = Point(0, 0)
    repr(center)
    Result:
    'Point(0, 0)'Pass Icon
  • We've designed the .__repr__ method to return legal Python code. In this case. that's Point(0, 0), which recreates the point object.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    center = Point(0, 0)
    center2 = eval(repr(center))
    (center2.x, center2.y)
    Result:
    (0, 0)Pass Icon
  • Note that when we build the "Point(...)" string, we call repr on self.x and self.y. This preserves the coordinates' data types. For example, we might use decimals instead of integers.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    from decimal import Decimal

    center_of_the_world = Point(Decimal(0), Decimal(0))
    repr(center_of_the_world)
    Result:
  • We can customize str's behavior in a similar way, by defining .__str__. But often, we don't have to! Conveniently, when an object has a .__repr__ but no .__str__, calling str gives us the .__repr__.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    gold_location = Point(10, 6)
    str(gold_location)
    Result:
    'Point(10, 6)'Pass Icon
  • repr can be quite verbose for large, complex objects. That's useful when debugging, where that detail helps us to see what's happening. But we wouldn't want to show all of that detail to a user. For strings that we show to users, we can define .__str__ with a more compact or simplified value.

  • >
    class Point:
    def __init__(self, x, y):
    self.x = x
    self.y = y

    def __repr__(self):
    return "Point(" + repr(self.x) + ", " + repr(self.y) + ")"

    def __str__(self):
    return "<" + str(self.x) + ", " + str(self.y) + ">"
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    gold_location = Point(10, 6)
    str(gold_location)
    Result:
    '<10, 6>'Pass Icon
  • In some cases, Python calls str automatically. For example, it calls str on {interpolated} values in f-strings. (In reality, Python actually calls the .__format__ dunder method. But in most cases, that just ends up calling str(...) for us.)

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    gold_location = Point(10, 6)
    message = f"Go digging at {gold_location}"
    message
    Result:
    'Go digging at <10, 6>'Pass Icon
  • Here's a code problem:

    Implement support for str and repr on User, using dunder methods and f-strings.

    • str(some_user) should return their name and email address in angle brackets, like "Amir <amir@example.com>".
    • repr(some_user) should return an expression that will recreate the user, like User('Amir', 'amir@example.com').
    • When defining .__repr__, remember to call repr on the attributes too. For example, if we create a strange user object with integers for its name and email address, we should get an appropriate repr result like User(1, 2).
    class User:
    def __init__(self, name, email):
    self.name = name
    self.email = email

    def __str__(self):
    return f"{self.name} <{self.email}>"

    def __repr__(self):
    return f"User({repr(self.name)}, {repr(self.email)})"
    amir = User("Amir", "amir@example.com")
    betty = User("Betty", "betty@example.com")
    strange_number_user = User(1, 2)

    assert str(amir) == "Amir <amir@example.com>"
    assert repr(amir) == "User('Amir', 'amir@example.com')"

    assert str(betty) == "Betty <betty@example.com>"
    assert repr(betty) == "User('Betty', 'betty@example.com')"

    assert str(strange_number_user) == "1 <2>"
    assert repr(strange_number_user) == "User(1, 2)"
    Goal:
    None
    Yours:
    NonePass Icon