19.3. OOP Format

  • Calling function format(obj, fmt) calls obj.__format__(fmt)

  • Method obj.__format__() must return str

  • Used for advanced formatting in f-strings (f'...')

19.3.1. Example

>>> class Astronaut:
...     def __init__(self, name):
...         self.name = name
...
...     def __format__(self, mood):
...         if mood == 'happy':
...             return f"Yuppi, we're going to space!"
...         elif mood == 'scared':
...             return f"I hope we don't crash"
>>>
>>>
>>> jose = Astronaut('José Jiménez')
>>>
>>> print(f'{jose:happy}')
Yuppi, we're going to space!
>>>
>>> print(f'{jose:scared}')
I hope we don't crash

19.3.2. Use Case - 1

  • ares3_landing = datetime(2035, 11, 7)

  • ares3_start = datetime(2035, 6, 29)

  • ares3_duration = timedelta(days=557, seconds=80025)

>>> SECOND = 1
>>> MINUTE = 60 * SECOND
>>> HOUR = 60 * MINUTE
>>> DAY = 24 * HOUR
>>> WEEK = 7 * DAY
>>> MONTH = 30.4375 * DAY
>>> YEAR = 365.25 * DAY
>>> SOL = 24*HOUR + 39*MINUTE + 35.244*SECOND
>>>
>>>
>>> class Mission:
...     def __init__(self, name, duration):
...         self.name = name
...         self.duration = duration
...
...     def __format__(self, unit):
...         duration = self.duration
...         match unit:
...             case 's'  | 'second' | 'seconds':  duration /= SECOND
...             case 'm'  | 'minute' | 'minutes':  duration /= MINUTE
...             case 'h'  | 'hour'   | 'hours':    duration /= HOUR
...             case 'd'  | 'day'    | 'days':     duration /= DAY
...             case 'w'  | 'week'   | 'weeks':    duration /= WEEK
...             case 'mth'| 'month'  | 'months':   duration /= MONTH
...             case 'y'  | 'year'   | 'years':    duration /= YEAR
...             case 'sol':                        duration /= SOL
...             case _: raise ValueError(f'Unknown unit')
...         return f'{duration:.1f} {unit}'
>>>
>>>
>>> ares3 = Mission('Ares III', duration=543*SOL)
>>>
>>> print(f'{ares3.name} mission to Mars took {ares3:seconds}')
Ares III mission to Mars took 48204957.5 seconds
>>>
>>> print(f'{ares3.name} mission to Mars took {ares3:minutes}')
Ares III mission to Mars took 803416.0 minutes
>>>
>>> print(f'{ares3.name} mission to Mars took {ares3:hours}')
Ares III mission to Mars took 13390.3 hours
>>>
>>> print(f'{ares3.name} mission to Mars took {ares3:days}')
Ares III mission to Mars took 557.9 days
>>>
>>> print(f'{ares3.name} mission to Mars took {ares3:weeks}')
Ares III mission to Mars took 79.7 weeks
>>>
>>> print(f'{ares3.name} mission to Mars took {ares3:months}')
Ares III mission to Mars took 18.3 months
>>>
>>> print(f'{ares3.name} mission to Mars took {ares3:years}')
Ares III mission to Mars took 1.5 years

19.3.3. Use Case - 2

  • Temperature conversion

>>> class Temperature:
...     def __init__(self, kelvin):
...         self.kelvin = kelvin
...
...     def to_fahrenheit(self):
...         return (self.kelvin-273.15) * 1.8 + 32
...
...     def to_celsius(self):
...         return self.kelvin - 273.15
...
...     def __format__(self, unit):
...         match unit:
...             case 'K' | 'kelvin':     value = self.kelvin
...             case 'C' | 'celsius':    value = self.to_celsius()
...             case 'F' | 'fahrenheit': value = self.to_fahrenheit()
...         unit = unit[0].upper()
...         return f'{value:.2f} {unit}'
>>>
>>>
>>> temp = Temperature(kelvin=309.75)
>>>
>>>
>>> print(f'Temperature is {temp:kelvin}')
Temperature is 309.75 K
>>>
>>> print(f'Temperature is {temp:celsius}')
Temperature is 36.60 C
>>>
>>> print(f'Temperature is {temp:fahrenheit}')
Temperature is 97.88 F

19.3.4. Use Case - 3

  • Format output

>>> from dataclasses import dataclass
>>> import json
>>>
>>>
>>> @dataclass
... class Point:
...     x: int
...     y: int
...     z: int = 0
...
...     def __format__(self, format):
...         match format:
...             case 'repr':  result = f"Point(x={self.x}, y={self.y}, z={self.z})"
...             case 'dict':  result = vars(self)
...             case 'tuple': result = tuple(vars(self).values())
...             case 'json':  result = json.dumps(vars(self))
...         return str(result)
>>>
>>>
>>> point = Point(x=1, y=2)
>>>
>>>
>>> print(f'{point:repr}')
Point(x=1, y=2, z=0)
>>>
>>> print(f'{point:tuple}')
(1, 2, 0)
>>>
>>> print(f'{point:dict}')
{'x': 1, 'y': 2, 'z': 0}
>>>
>>> print(f'{point:json}')
{"x": 1, "y": 2, "z": 0}

19.3.5. Use Case - 1

  • status "Full Health" - when health 100%

  • status "Injured" - when health 75% - 100% (exclusive)

  • status "Badly Wounded" - when health 25% - 75% (exclusive)

  • status "Near Death" - when health 1% - 25% (exclusive)

  • status "Dead" - when health 0% or less

>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class Hero:
...     name: str
...     health: int = 100
...     health_full: int = 100
...
...     def _get_status(self):
...         percent = int(self.health / self.health_full * 100)
...         if percent == 100: return 'full health'
...         if percent in range(75, 100): return 'injured'
...         if percent in range(25, 75): return 'badly wounded'
...         if percent in range(1, 25): return 'near death'
...         if percent <= 0: return 'dead'
...
...     def __format__(self, what):
...         match what:
...             case 'status': return self._get_status()
...             case 'name': return self.name
...             case _: return self.name
>>>
>>>
>>> hero = Hero('Pan Twardowski')
>>> hero.health = 100
>>> print(f'{hero:name} is {hero:status}')
Pan Twardowski is full health
>>> hero.health = 90
>>> print(f'{hero:name} is {hero:status}')
Pan Twardowski is injured
>>> hero.health = 50
>>> print(f'{hero:name} is {hero:status}')
Pan Twardowski is badly wounded
>>> hero.health = 10
>>> print(f'{hero:name} is {hero:status}')
Pan Twardowski is near death
>>> hero.health = 0
>>> print(f'{hero:name} is {hero:status}')
Pan Twardowski is dead

19.3.6. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Operator String Format
# - Difficulty: easy
# - Lines: 8
# - Minutes: 5

# %% English
# 1. Overload `format()`
# 2. Has to convert length units: km, cm, m
# 3. Round result to one decimal place
# 4. Run doctests - all must succeed

# %% Polish
# 1. Przeciąż `format()`
# 2. Ma konwertować jednostki długości: km, cm, m
# 3. Wynik zaokrąglij do jednego miejsca po przecinku
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Hints
# - 1 km = 1000 m
# - 1 m = 100 cm

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> result = Distance(meters=1337)
>>> format(result, 'km')
'1.3 km'
>>> format(result, 'cm')
'133700.0 cm'
>>> format(result, 'm')
'1337.0 m'
"""

METER = 1
CENTIMETER = METER * 0.01
KILOMETER = METER * 1000


class Distance:
    meters: int | float

    def __init__(self, meters):
        self.meters = meters