Skip to content

Love Your Representation

Christian Ledermann edited this page Nov 3, 2023 · 39 revisions

A dinosaur in the vaults of paranassus

Embrace the power of __repr__ Python Representation

There are two ways out that Python provides us with, out of the box, to convert an object into a string: __str__ and __repr__. __str__ gets all the love, __repr__ is a bit more obscure. Python loves string

What is the difference anyway?

  • __str__ is meant to define the "informal" or user-friendly string representation of an object. It's used when you want to provide a human-readable description of the object.
  • __repr__ is meant for the "formal" or unambiguous string representation of an object. It's used for debugging, development, and to represent the object in a way that could be used to recreate the same object.

Think of a Python object as a toy. When we want to describe this toy to someone, we can use two ways:

__str__ is like giving a friendly and simple description of the toy. It's what we tell our friends when we want them to know what the toy looks like or what it does. It's like saying, "This toy is colourful and fun to play with!" Python toy

__repr__ is like giving a very detailed and technical description of the toy. It's what we tell someone who needs to know all the nitty-gritty details about the toy. A look under the hood. Tech Python

Out of the box, python provides an unambiguous representation.

>>> class Simple:
...     pass
... 
>>> simple = Simple()
>>> simple
<__main__.Simple object at 0x7f3d3b5fa230>
>>> 

That is not much help.

A better example is the datetime module.

>>> import datetime
>>> now = datetime.datetime.utcnow()
>>> now
datetime.datetime(2023, 11, 3, 18, 55, 45, 862092)
>>> 

Data classes provide a nice implementation.

>>> from dataclasses import dataclass
>>> 
>>> @dataclass
... class InventoryItem:
...     """Class for keeping track of an item in inventory."""
...     name: str
...     unit_price: float
...     quantity_on_hand: int = 0
... 
>>> ii = InventoryItem('python', 897)
>>> ii
InventoryItem(name='python', unit_price=897, quantity_on_hand=0)

Weird python

In the Python Standard Library, we also find some weird examples

>>> from enum import Enum
>>> 
>>> class DataType(Enum):
...     string = "string"
...     int_ = "int"
...     uint = "uint"
...     short = "short"
...     ushort = "ushort"
...     float_ = "float"
...     double = "double"
...     bool_ = "bool"
... 
>>> ui = DataType('uint')
>>> ui
<DataType.uint: 'uint'>

But that can be overridden (this is copied from: "3.12.0 Documentation » The Python Standard Library » Data Types » enum — Support for enumerations")

>>> from enum import auto
>>> class OtherStyle(Enum):
...     ALTERNATE = auto()
...     OTHER = auto()
...     SOMETHING_ELSE = auto()
...     def __repr__(self):
...         cls_name = self.__class__.__name__
...         return f'{cls_name}.{self.name}'
... 
>>> OtherStyle.ALTERNATE
OtherStyle.ALTERNATE

Another example is fastkml

>>> from fastkml.gx import Track
>>> doc = """
...     <gx:Track xmlns:gx="http://www.google.com/kml/ext/2.2"
...         xmlns:kml="http://www.opengis.net/kml/2.2">
...         <kml:when>2010-05-28T02:02:09Z</kml:when>
...         <kml:when>2010-05-28T02:02:56Z</kml:when>
...         <kml:when />
...         <gx:angles>45.54 66.23 77.0</gx:angles>
...         <gx:angles />
...         <gx:angles>75.54 86.23 17.0</gx:angles>
...         <gx:coord>-122.20 37.37 156.00</gx:coord>
...         <gx:coord>-122.20 37.37 152.00</gx:coord>
...         <gx:coord>-122.20 37.37 147.00</gx:coord>
...     </gx:Track>
... """
>>> track = Track.from_string(doc, ns="")
>>> track
Track(
    ns="",
    id="",
    target_id="",
    extrude=None,
    tessellate=None,
    altitude_mode=None,
    track_items=[
        TrackItem(
            when=datetime.datetime(2010, 5, 28, 2, 2, 9, tzinfo=tzutc()),
            coord=Point(-122.2, 37.37, 156.0),
            angle=Angle(heading=45.54, tilt=66.23, roll=77.0),
        ),
        TrackItem(
            when=datetime.datetime(2010, 5, 28, 2, 2, 56, tzinfo=tzutc()),
            coord=Point(-122.2, 37.37, 152.0),
            angle=None,
        ),
        TrackItem(
            when=None,
            coord=Point(-122.2, 37.37, 147.0),
            angle=Angle(heading=75.54, tilt=86.23, roll=17.0),
        ),
    ],
)

This could be the end. Just add a __repr__ that mirrors the __init_ signature of the class. The End?

That looks like a lot of ungrateful, repetitive manual labour. Manual labour

Being a pythonista - I’m too lazy to spend 8 hours mindlessly performing a function, but not too lazy to spend 16 hours automating it. A Python coding

crepr

It is pronounced like crêpe, the French pancake. crepe

A Python script that takes a module name as a command-line argument, imports the specified module, and then prints a __repr__ method for each class defined in the module. It uses the definition found in the __init__ method of the class.

crepr kw_only

For a class definition like

"""Test class for kw only arguments."""
class KwOnly:
    """The happy path class."""

    def __init__(self, name: str, *, age: int) -> None:
        """Initialize the class."""
        self.name = name  # pragma: no cover
        self.age = age  # pragma: no cover

The output is

"""Test class for kw only arguments."""
class KwOnly:
    """The happy path class."""

    def __init__(self, name: str, *, age: int) -> None:
        """Initialize the class."""
        self.name = name  # pragma: no cover
        self.age = age  # pragma: no cover

    # crepr generated __repr__ for class: KwOnly
    def __repr__(self) -> str:
        """(C)representation of the object"""
        return (f'{self.__class__.__name__}('
            f'name={self.name!r}, '
            f'age={self.age!r}, '
        ')')

It is Unix-ish, does just one thing: it prints the modified file to StdOut so you can redirect or pipe the output. Unix python

The code is tidy and clean, follows best practises, is fully tested and type checked as a hypermodern package should be. hyper modern kitchen

You don't have to work in an unsafe environment when you contribute to this project. burning kitchen

It is still in its infancy, but you can install it from PyPi. Baby python

That is it, Give your representations some love. ❤️.__repr__(self) -> str: Love your repr