16.10. OOP Stringify Objects

16.10.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'
>>>
>>> print(date)
1961-04-12
>>>
>>>
>>> repr(date)
'datetime.date(1961, 4, 12)'
>>>
>>> date
datetime.date(1961, 4, 12)

16.10.2. String

  • Calling function print(obj) calls str(obj)

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

  • Method obj.__str__() must return str

  • This is dedicated for end-user of your class

>>> 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, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
>>>
>>>
>>> astro = Astronaut('Mark', 'Watney')
>>>
>>> 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, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
...
...     def __str__(self):
...         return f'Hello {self.firstname} {self.lastname}'
>>>
>>>
>>> astro = Astronaut('Mark', 'Watney')
>>>
>>> print(astro)
Hello Mark Watney
>>>
>>> str(astro)
'Hello Mark Watney'
>>>
>>> astro.__str__()
'Hello Mark Watney'

16.10.3. Representation

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

  • Method obj.__repr__() must return str

  • Dedicated for developers

  • Shows 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:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
>>>
>>>
>>> astro = Astronaut('Mark', 'Watney')
>>>
>>> repr(astro)  
'<Astronaut object at 0x...>'

Using __repr__() on a class:

>>> class Astronaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
...
...     def __repr__(self):
...         firstname = self.firstname
...         lastname = self.lastname
...         return f'Astronaut({firstname=}, {lastname=})'
>>>
>>>
>>> astro = Astronaut('Mark', 'Watney')
>>>
>>> repr(astro)
"Astronaut(firstname='Mark', lastname='Watney')"
>>>
>>> astro
Astronaut(firstname='Mark', lastname='Watney')

16.10.4. Printing Sequence Elements

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

>>> class Astronaut:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
>>>
>>>
>>> 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, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
...
...     def __repr__(self):
...         return f'{self.firstname} {self.lastname}'
>>>
>>>
>>> crew = [Astronaut('Jan', 'Twardowski'),
...         Astronaut('Mark', 'Watney'),
...         Astronaut('Melissa', 'Lewis')]
>>>
>>> print(crew)
[Jan Twardowski, Mark Watney, Melissa Lewis]

16.10.5. Assignments

Code 16.15. 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 16.16. 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')"
"""

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
    label: str

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


Code 16.17. 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