Love Your Representation
Tags: python, opensource, cleancode, observability
Published: 2023-11-04 21:39:15

---
title: Love Your Representation
published: true
description: Embrace the power of `__repr__` 
tags: python, opensource, cleancode, observability
cover_image: https://github.com/cleder/talks/blob/main/crepr/python-representation.jpg?raw=true
# Use a ratio of 100:42 for best results.
# published_at: 2023-11-03 22:55 +0000
---
Embrace the power of `__repr__`

## Usage

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
>>> class Simple:
...     def __init__(self, name):
...             self.name = name
...     def __str__(self):
...             return f'My name is {self.name}'
...     def __repr__(self):
...             return f'Simple({self.name})'
... 
>>> simple = Simple('Christian')
```


| `__str__`  | `__repr__` |  
|------------|------------|
| `print(simple)` | `repr(simple)`| 
| `'%s' % simple`  | `'%r' % simple` |  
| `'{}'.format(simple)` | `'{!r}'.format(simple)` |  
| `f'{simple}'` | `f'{simple!r}'` |  
| Output |    
| `'My name is Christian'` | `'Simple(Christian)'` |    

F-Srings in newer Python versions also provide a nice shortcut: `f'{simple=}'` will produce `'simple=Simple(Christian)'`.


![Python loves string](https://github.com/cleder/talks/blob/main/crepr/love-string.jpg?raw=true)

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](https://github.com/cleder/talks/blob/main/crepr/toy-python.jpg?raw=true)

`__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](https://github.com/cleder/talks/blob/main/crepr/tech-python.jpg?raw=true)

## Why should I care about implementing a `__repr__`?

Implementing a `__repr__` method for your Python classes can be valuable for several reasons, particularly in debugging, development, and logging:

- **Debugging**: When you're debugging your code, having a well-defined `__repr__` method can provide you with a clear and informative representation of an object's state. This can help you quickly identify issues and understand the data stored in an object, making debugging more efficient.
- **Development**: During development, you may frequently need to inspect the contents of objects for testing and problem-solving. A useful `__repr__` method can simplify this process by displaying relevant object details, helping you develop and fine-tune your code more effectively.
- **Logging**: When you log information about your program's execution, having a good `__repr__` method can be invaluable. Logging the string representation of objects can help you track the program's state, record important data, and diagnose issues when they occur.
- **Readable Output**: A well-implemented `__repr__` method can provide human-readable output, making it easier to understand the state of objects. This is especially useful when you need to inspect the program's behavior during development or debugging.
- **Interactive Environments**: In interactive Python environments like Jupyter notebooks, IPython, and REPL, the `__repr__` method is used automatically when you evaluate an object. This makes it convenient to understand and explore the object's content without explicitly calling the `__repr__` method.
- **Documentation and Exploration**: When working with libraries and frameworks, having a descriptive `__repr__` can enhance the documentation and help others understand how to use your classes effectively. It also makes it easier for colleagues or users to explore and experiment with your code.

Implementing a meaningful `__repr__` method is a good practice because it provides a clear and informative way to represent your objects, making your code more maintainable, debuggable, and user-friendly. It's especially useful when you need to interact with and understand objects during the development and debugging process.

![Inspection of a python](https://github.com/cleder/talks/blob/main/crepr/inspect-python.jpg?raw=true)

## Examples From The Python Standard Library

An example is the `datetime` module.

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

Data classes provide a nice implementation.

```python
>>> 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](https://github.com/cleder/talks/blob/main/crepr/weird-python.jpg?raw=true)

Even in the Python Standard Library the implementation of `__repr__` may vary.

```python
>>> from enum import Enum
>>> 
>>> class DataType(Enum):
...     string = "string"
...     int_ = "int"
...     uint = "uint"
...     short = "short"
... 
>>> ui = DataType('uint')
>>> ui
<DataType.uint: 'uint'>
```

This is not undisputed and can be overridden (this is copied from: [3.12.0 Documentation » The Python Standard Library » Data Types » enum — Support for enumerations](https://docs.python.org/3/library/enum.html#enum.Enum.__repr__))

```python
>>> 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
```

### ![Polished python](https://github.com/cleder/talks/blob/main/crepr/polished-python.jpg?raw=true)

### Example

[fastkml](https://github.com/cleder/fastkml) is a library to work with `<KML />` a geospatial `<XML />` format. 

```python
>>> 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>
... """
```

Out of the box, python provides an unambiguous representation.

```python
>>> track = Track.from_string(doc, ns="")
<fastkml.gx.Track object at 0x7f3d3b5fa230>
>>> 
```

That is not much help.

The `__init__` method of this class is defined as:

```python
class Track(_Geometry):
    """A track describes how an object moves through the world over a given time period."""
    def __init__(
        self,
        *,
        ns: Optional[str] = None,
        id: Optional[str] = None,
        target_id: Optional[str] = None,
        extrude: Optional[bool] = False,
        tessellate: Optional[bool] = False,
        altitude_mode: Optional[AltitudeMode] = None,
        track_items: Optional[Sequence[TrackItem]] = None,
    ) -> None:
```

When we define the `__repr__` like

```python
    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}("
            f"ns={self.ns!r}, "
            f"id={self.id!r}, "
            f"target_id={self.target_id!r}, "
            f"extrude={self.extrude!r}, "
            f"tessellate={self.tessellate!r}, "
            f"altitude_mode={self.altitude_mode}, "
            f"track_items={self.track_items!r}"
            ")"
        )
```

The representation looks much better

```python
>>> track = Track.from_string(doc, ns="")
>>> track
Track(
    ns="http://www.google.com/kml/ext/2.2",
    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),
        ),
    ],
)
```


The pattern is simple, construct a string out of the objects attributes for each parameter in the `__init__` method.

This could be the end. Just add a `__repr__` that mirrors the `__init_` signature of the class. There is nothing much to it, easily done and it will save you time and effort in the future when you have to debug your code or figure out what caused a bug from the log files.

![The End?](https://github.com/cleder/talks/blob/main/crepr/end-python.jpg?raw=true)

Implementing this pattern to your existing code looks like a lot of tedious, error-prone, ungrateful, repetitive, manual labour. It is a major chore when your project has dozens of classes, let alone hundreds or thousands. 

![Manual labour](https://github.com/cleder/talks/blob/main/crepr/manual-labour.jpg?raw=true)

> Being a developer, I’m too lazy to spend 8 hours mindlessly performing a function, but not too lazy to spend 16 hours automating it.
> <cite>[Timothy Crosley](https://github.com/timothycrosley)</cite>

![A Python coding](https://github.com/cleder/talks/blob/main/crepr/scriting-pyhon.jpg?raw=true)

## [crepr](https://github.com/cleder/crepr)

It is pronounced "kraypr" (/kɹeɪpr/) like Americans and Australians pronounce crêpe, the French pancake. 

[![XKCD crepe ](https://imgs.xkcd.com/comics/crepe.png)](https://xkcd.com/2619/)

![crepe](https://github.com/cleder/talks/blob/main/crepr/crepe.jpg?raw=true)

A Python script that takes a file name as a command-line argument, imports the specified module, and then prints a `__repr__` method for each class defined in the module. It creates the `__repr__` by inspecting the keyword arguments of the `__init__` method of the class.

### Example

Given the file `tests/classes/kw_only_test.py` with the contents:

```python
class KwOnly:
    def __init__(self, name: str, *, age: int) -> None:
        self.name = name
        self.age = age
```

The command:

```bash
❯ crepr tests/kw_only_test.py
```

produces

```python
class KwOnly:
    def __init__(self, name: str, *, age: int) -> None:
        self.name = name
        self.age = age

    def __repr__(self) -> str:
        """Create a string (c)representation for KwOnly."""
        return (f'{self.__class__.__module__}.{self.__class__.__name__}('
            f'name={self.name!r}, '
            f'age={self.age!r}, '
        ')')
```

The `repr()` of an instance of this class will be:

```python
>>> from tests.classes.kw_only_test import KwOnly
>>> kwo = KwOnly('Christian', age=25)
>>> kwo
tests.classes.kw_only_test.KwOnly(name='Christian', age=25, )
```

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](https://github.com/cleder/talks/blob/main/crepr/unix-python.jpg?raw=true)


The code is tidy and clean, follows best practises, is fully tested and type checked as a [hypermodern](https://youtu.be/uwmQgCrCh2s?si=usohCOVmJK0Z4jPx) package should be.

![hyper modern kitchen](https://github.com/cleder/talks/blob/main/hypermodern-kitchen.jpg?raw=true)

It is still in its infancy, but you can install it from [PyPI](https://pypi.org/project/crepr/).

![Baby python](https://github.com/cleder/talks/blob/main/crepr/baby-python.jpg?raw=true)

## Summary 

Implementing a `__repr__` method is a best practice as it improves the debugging and development process, enhances code readability, and aids in effective communication of your object's structure and state. Do not forget to include the `!r` qualifier when logging your objects.

Give your representations some love.

❤️`.__repr__(self) -> str:`

![Love your repr](https://github.com/cleder/talks/blob/main/crepr/love-repr-python.jpg?raw=true)