Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Detailing Official String Representation With .__repr__()

Avatar image for Andras

Andras on Jan. 10, 2025

You don’t have to create the instance.value attribute because instance is already a float and represents the value. This is enough:

class Storage(float):
    def __new__(cls, value, unit):
        instance = super().__new__(cls, value)
        instance.unit = unit
        return instance

    def __str__(self):
        return f"{super().__repr__()} {self.unit}"

    def __repr__(self):
        return f'Storage({super().__repr__()}, "{self.unit}")'

In the REPL:

>>> storage = Storage(512, "GB")
>>> str(storage)
'512.0 GB'
>>> repr(storage)
'Storage(512.0, "GB")'

The only tricky bit with this code, which I don’t fully understand yet, is that you have to call super().__repr__() in both cases; this is the only way it works as expected. Naively, you would think that since float.__str__() and float.__repr__() gives the same result, you could call either of these in either methods. But if you do, it will either produce 'Storage(512.0, "GB") GB' or RecursionError. I would be very much interested what happens in these cases in detail…

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on Jan. 13, 2025

@Andras When you call super.__str__() in your .__repr__() method, you essentially bypass your custom .__str__() method defined within the same class. Instead, the processing goes directly to the parent’s class implementation or object.__str__(), which by default calls your .__repr__() method again, causing a recursion error. One way to break that cycle is to call self.__str__() instead of super().__str__() or (better yet) str(self), like so:

class Storage(float):
    def __new__(cls, value, unit):
        instance = super().__new__(cls, value)
        instance.unit = unit
        return instance

    def __str__(self):
        return f"{super().__repr__()} {self.unit}"

    def __repr__(self):
        return f'Storage({str(self)}, "{self.unit}")'

What’s confusing about this is that both str() and repr() functions will call different special methods depending on which ones are available.

Become a Member to join the conversation.