13.8. OOP Stringify Objects

13.8.1. Rationale

  • str(obj) -> obj.__str__()

  • repr(obj) -> obj.__repr__()

  • print(obj) -> str(obj) -> obj.__str__()

>>> import datetime
>>> date = datetime.date(1961, 4, 12)
>>>
>>>
>>> str(date)
'1961-04-12'
>>>
>>> repr(date)
'datetime.date(1961, 4, 12)'
>>>
>>> print(date)
1961-04-12

13.8.2. String

  • Calling function str(obj) calls obj.__str__()

  • Calling function print(obj) calls str(obj), which calls obj.__str__()

  • Method obj.__str__() must return str

  • for end-user

>>> class Astronaut:
...     pass
>>>
>>> astro = Astronaut()
>>> str(astro)  
'<Astronaut object at 0x...>'

Object without __str__() method overloaded prints their memory address:

>>> class Astronaut:
...     def __init__(self, name):
...         self.name = name
>>>
>>>
>>> astro = Astronaut('José Jiménez')
>>>
>>> print(astro)  
<Astronaut object at 0x...>
>>> str(astro)  
'<Astronaut object at 0x...>'
>>> astro.__str__()  
'<Astronaut object at 0x...>'

Objects can verbose print if __str__() method is present:

>>> class Astronaut:
...     def __init__(self, name):
...         self.name = name
...
...     def __str__(self):
...         return f'My name... {self.name}'
>>>
>>>
>>> astro = Astronaut('José Jiménez')
>>>
>>> print(astro)
My name... José Jiménez
>>> str(astro)
'My name... José Jiménez'
>>> astro.__str__()
'My name... José Jiménez'

13.8.3. Representation

  • Calling function repr(obj) calls obj.__repr__()

  • Method obj.__repr__() must return str

  • for developers

  • object representation

  • copy-paste for creating object with the same values

  • useful for debugging

  • printing list will call __repr__() method on each element

>>> class Astronaut:
...     pass
>>>
>>> astro = Astronaut()
>>> repr(astro)  
'<Astronaut object at 0x...>'

Using __repr__() on a class:

>>> class Astronaut:
...     def __init__(self, name):
...         self.name = name
...
...     def __repr__(self):
...         return f'Astronaut(name="{self.name}")'
>>>
>>>
>>> astro = Astronaut('José Jiménez')
>>>
>>> repr(astro)
'Astronaut(name="José Jiménez")'
>>> astro
Astronaut(name="José Jiménez")

Printing list will call __repr__() method on each element:

>>> class Astronaut:
...     def __init__(self, name):
...         self.name = name
>>>
>>> crew = [Astronaut('Jan Twardowski'),
...         Astronaut('Mark Watney'),
...         Astronaut('Melissa Lewis')]
>>>
>>> print(crew)  
[<Astronaut object at 0x...>, <Astronaut object at 0x...>, <Astronaut object at 0x...>]

Printing list will call __repr__() method on each element:

>>> class Astronaut:
...     def __init__(self, name):
...         self.name = name
...
...     def __repr__(self):
...         return f'{self.name}'
>>>
>>> crew = [Astronaut('Jan Twardowski'),
...         Astronaut('Mark Watney'),
...         Astronaut('Melissa Lewis')]
>>>
>>> print(crew)
[Jan Twardowski, Mark Watney, Melissa Lewis]

13.8.4. Assignments

Code 13.14. Solution
"""
* Assignment: OOP Stringify Str
* Required: yes
* Complexity: easy
* Lines of code: 3 lines
* Time: 5 min

English:
    1. While printing object show: species name and a sum of `self.features`
    2. Result of sum round to one decimal place
    3. Run doctests - all must succeed

Polish:
    1. Przy wypisywaniu obiektu pokaż: nazwę gatunku i sumę `self.features`
    2. Wynik sumowania zaokrąglij do jednego miejsca po przecinku
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> for *features, label in DATA:
    ...     iris = Iris(features, label)
    ...     print(iris)
    setosa 9.4
    versicolor 16.3
    virginica 19.3
"""

DATA = [
    (4.7, 3.2, 1.3, 0.2, 'setosa'),
    (7.0, 3.2, 4.7, 1.4, 'versicolor'),
    (7.6, 3.0, 6.6, 2.1, 'virginica'),
]


class Iris:
    def __init__(self, features, label):
        self.features = features
        self.label = label


Code 13.15. Solution
"""
* Assignment: OOP Stringify Repr
* Required: yes
* Complexity: easy
* Lines of code: 3 lines
* Time: 5 min

English:
    1. Overload `repr()`
    2. Run doctests - all must succeed

Polish:
    1. Przeciąż `repr()`
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass, ismethod

    >>> assert isclass(Iris)
        >>> iris = Iris(DATA)

    >>> assert hasattr(Iris, '__repr__')
    >>> assert ismethod(iris.__repr__)
    >>> repr(iris)
    "Iris(features=[4.7, 3.2, 1.3, 0.2], label='setosa')"
"""
from typing import Any, List


DATA = (4.7, 3.2, 1.3, 0.2, 'setosa')

# repr() -> Iris(features=[4.7, 3.2, 1.3, 0.2], label='setosa')
class Iris:
    features: list[float]
    label: str

    def __init__(self, data):
        self.features = list(data[:-1])
        self.label = str(data[-1])


Code 13.16. Solution
"""
* Assignment: OOP Stringify Nested
* Required: yes
* Complexity: medium
* Lines of code: 9 lines
* Time: 21 min

English:
    1. Overload `str` and `repr` to achieve desired printing output
    2. Run doctests - all must succeed

Polish:
    1. Przeciąż `str` i `repr` aby osiągnąć oczekiwany rezultat wypisywania
    2. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * Define `Crew.__str__()`
    * Define `Astronaut.__str__()` and `Astronaut.__repr__()`
    * Define `Mission.__repr__()`

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> melissa = Astronaut('Melissa Lewis')
    >>> print(f'Commander: \\n{melissa}\\n')  # doctest: +NORMALIZE_WHITESPACE
    Commander:
    Melissa Lewis

    >>> mark = Astronaut('Mark Watney', experience=[
    ...     Mission(2035, 'Ares 3')])
    >>> print(f'Space Pirate: \\n{mark}\\n')  # doctest: +NORMALIZE_WHITESPACE
    Space Pirate:
    Mark Watney veteran of [
          2035: Ares 3]

    >>> crew = Crew([
    ...     Astronaut('Jan Twardowski', experience=[
    ...         Mission(1969, 'Apollo 11'),
    ...         Mission(2024, 'Artemis 3'),
    ...     ]),
    ...     Astronaut('José Jiménez'),
    ...     Astronaut('Mark Watney', experience=[
    ...         Mission(2035, 'Ares 3'),
    ...     ]),
    ... ])

    >>> print(f'Crew: \\n{crew}')  # doctest: +NORMALIZE_WHITESPACE
    Crew:
    Jan Twardowski veteran of [
          1969: Apollo 11,
          2024: Artemis 3]
    José Jiménez
    Mark Watney veteran of [
          2035: Ares 3]
"""


class Crew:
    def __init__(self, members=()):
        self.members = list(members)


class Astronaut:
    def __init__(self, name, experience=()):
        self.name = name
        self.experience = list(experience)


class Mission:
    def __init__(self, year, name):
        self.year = year
        self.name = name