10.3. Operator Stringify¶
str()
repr()
format()
print()
+
- add-
- sub*
- mul%
- mod+=
- iadd-=
- isub*=
- imul%=
- imod
10.3.1. About¶
Operator |
Method |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10.3.2. Example¶
SetUp:
>>> import datetime
>>>
>>>
>>> date = datetime.date(1961, 4, 12)
>>> print(date)
1961-04-12
>>> str(date)
'1961-04-12'
>>> repr(date)
'datetime.date(1961, 4, 12)'
>>> format(date, '%Y-%m-%d')
'1961-04-12'
10.3.3. String¶
Calling function
str(obj)
callsobj.__str__()
Calling function
print(obj)
callsstr(obj)
, which callsobj.__str__()
Method
obj.__str__()
must returnstr
for end-user
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)
<__main__.Astronaut object at 0x...>
>>>
>>> str(astro)
'<__main__.Astronaut object at 0x...>'
>>>
>>> astro.__str__()
'<__main__.Astronaut object at 0x...>'
>>>
>>> f'{astro!s}'
'<__main__.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'
>>>
>>> f'{astro!s}'
'My name... José Jiménez'
10.3.4. Representation¶
Calling function
repr(obj)
callsobj.__repr__()
Method
obj.__repr__()
must returnstr
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:
... def __init__(self, name):
... self.name = name
>>>
>>>
>>> astro = Astronaut('José Jiménez')
>>>
>>> repr(astro)
'<__main__.Astronaut object at 0x...>'
>>>
>>> astro
<__main__.Astronaut object at 0x...>
>>>
>>> f'{astro!r}'
'<__main__.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")
>>>
>>> f'{astro!r}'
'Astronaut(name="José Jiménez")'
10.3.5. Format¶
Calling function
format(obj, fmt)
callsobj.__format__(fmt)
Method
obj.__format__()
must returnstr
Used for advanced formatting
>>> 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
10.3.6. Nested¶
Printing
list
will call__repr__()
method on each element
>>> data = [1,2,3]
>>> print(data)
[1, 2, 3]
Printing list
will call __repr__()
method on each element:
>>> class Astronaut:
... def __init__(self, name):
... self.name = name
>>>
>>>
>>> crew = [Astronaut('Mark Watney'),
... Astronaut('Melissa Lewis'),
... Astronaut('Rick Martinez')]
>>>
>>> print(crew)
[<__main__.Astronaut object at 0x...>, <__main__.Astronaut object at 0x...>, <__main__.Astronaut object at 0x...>]
>>> class Astronaut:
... def __init__(self, name):
... self.name = name
...
... def __repr__(self):
... return f'{self.name}'
>>>
>>> crew = [Astronaut('Mark Watney'),
... Astronaut('Melissa Lewis'),
... Astronaut('Rick Martinez')]
>>>
>>> print(crew)
[Mark Watney, Melissa Lewis, Rick Martinez]
10.3.7. Use Case - 0x01¶
%
(__mod__
) operator behavior forint
andstr
:
>>> 13 % 4
1
>>>
>>> '13' % '4'
Traceback (most recent call last):
TypeError: not all arguments converted during string formatting
>>> pi = 3.1514
>>>
>>>
>>> 'String: %s' % pi
'String: 3.1514'
>>>
>>> 'Double: %d' % pi
'Double: 3'
>>>
>>> 'Float: %f' % pi
'Float: 3.151400'
>>> firstname = 'Mark'
>>> lastname = 'Watney'
>>>
>>>
>>> 'Hello %s' % firstname
'Hello Mark'
>>>
>>> 'Hello %s %s' % (firstname, lastname)
'Hello Mark Watney'
>>>
>>> 'Hello %(fname)s %(lname)s' % {'fname': firstname, 'lname': lastname}
'Hello Mark Watney'
>>> text = 'Hello %s'
>>> text %= 'Mark Watney'
>>>
>>> print(text)
Hello Mark Watney
>>> class Str:
... def __mod__(self, other):
... """str substitute"""
...
... if type(other) is str:
... ...
... if type(other) is tuple:
... ...
... if type(other) is dict:
... ...
Note, that using %s
, %d
, %f
is currently deprecated in favor
of f'...'
string formatting. More information in Builtin Printing
10.3.8. Use Case - 0x02¶
Self formatting duration
>>> from dataclasses import dataclass
>>>
>>>
>>> SECOND = 1
>>> MINUTE = 60 * SECOND
>>> HOUR = 60 * MINUTE
>>> DAY = 24 * HOUR
>>> MONTH = 30.4375 * DAY
>>> YEAR = 365.25 * DAY
>>>
>>> SOL = 24*HOUR + 39*MINUTE + 35*SECOND
>>>
>>>
>>> @dataclass
... class Duration:
... seconds: int
...
... def __format__(self, unit):
... if unit == 'seconds':
... result = self.seconds / SECOND
... if unit == 'minutes':
... result = self.seconds / MINUTE
... elif unit == 'hours':
... result = self.seconds / HOUR
... elif unit == 'days':
... result = self.seconds / DAY
... elif unit == 'months':
... result = self.seconds / MONTH
... elif unit == 'years':
... result = self.seconds / YEAR
... return f'{result:.1f} {unit}'
>>>
>>>
>>> ares3 = Duration(543*SOL)
>>>
>>> print(f'Ares3 mission to Mars took {ares3:seconds}')
Ares3 mission to Mars took 48204825.0 seconds
>>>
>>> print(f'Ares3 mission to Mars took {ares3:minutes}')
Ares3 mission to Mars took 803413.8 minutes
>>>
>>> print(f'Ares3 mission to Mars took {ares3:hours}')
Ares3 mission to Mars took 13390.2 hours
>>>
>>> print(f'Ares3 mission to Mars took {ares3:days}')
Ares3 mission to Mars took 557.9 days
>>>
>>> print(f'Ares3 mission to Mars took {ares3:months}')
Ares3 mission to Mars took 18.3 months
>>>
>>> print(f'Ares3 mission to Mars took {ares3:years}')
Ares3 mission to Mars took 1.5 years
10.3.9. Use Case - 0x03¶
Duration Many Units
>>> from dataclasses import dataclass
>>>
>>>
>>> SECOND = 1
>>> MINUTE = 60 * SECOND
>>> HOUR = 60 * MINUTE
>>> DAY = 24 * HOUR
>>> MONTH = 30.4375 * DAY
>>> YEAR = 365.25 * DAY
>>>
>>> SOL = 24*HOUR + 39*MINUTE + 35*SECOND
>>>
>>>
>>> @dataclass
... class Duration:
... seconds: int
...
... def __format__(self, unit):
... duration = self.seconds
... if unit in ('seconds', 'second', 'sec', 's'):
... duration /= SECOND
... elif unit in ('minutes', 'minute', 'min', 'm'):
... duration /= MINUTE
... elif unit in ('hours', 'hour', 'hr', 'h'):
... duration /= HOUR
... elif unit in ('days', 'day', 'd'):
... duration /= DAY
... elif unit in ('months', 'month', 'mth'):
... duration /= MONTH
... elif unit in ('years', 'year', 'yr', 'y'):
... duration /= YEAR
... else:
... raise ValueError('Invalid unit')
... return f'{duration:.1f} {unit}'
>>>
>>>
>>> ares3 = Duration(543*SOL)
>>>
>>> print(f'Ares3 mission to Mars took {ares3:seconds}')
Ares3 mission to Mars took 48204825.0 seconds
>>>
>>> print(f'Ares3 mission to Mars took {ares3:minutes}')
Ares3 mission to Mars took 803413.8 minutes
>>>
>>> print(f'Ares3 mission to Mars took {ares3:hours}')
Ares3 mission to Mars took 13390.2 hours
>>>
>>> print(f'Ares3 mission to Mars took {ares3:days}')
Ares3 mission to Mars took 557.9 days
>>>
>>> print(f'Ares3 mission to Mars took {ares3:months}')
Ares3 mission to Mars took 18.3 months
>>>
>>> print(f'Ares3 mission to Mars took {ares3:years}')
Ares3 mission to Mars took 1.5 years
10.3.10. Use Case - 0x04¶
ares3_landing = datetime(2035, 11, 7)
ares3_start = datetime(2035, 6, 29)
>>> from dataclasses import dataclass
>>>
>>>
>>> SECOND = 1
>>> MINUTE = 60 * SECOND
>>> HOUR = 60 * MINUTE
>>> DAY = 24 * HOUR
>>> MONTH = 30.4375 * DAY
>>> YEAR = 365.25 * DAY
>>>
>>> SOL = 24*HOUR + 39*MINUTE + 35*SECOND
>>>
>>>
>>> @dataclass
... class Duration:
... seconds: int
...
... def __format__(self, unit: str) -> str:
... duration = self.seconds
... match unit:
... case 'seconds': duration /= SECOND
... case 'minutes': duration /= MINUTE
... case 'hours': duration /= HOUR
... case 'days': duration /= DAY
... case 'months': duration /= MONTH
... case 'years': duration /= YEAR
... case _: raise ValueError('Invalid unit')
... return f'{duration:.1f} {unit}'
>>>
>>>
>>> ares3 = Duration(543*SOL)
>>>
>>> print(f'Ares3 mission to Mars took {ares3:seconds}')
Ares3 mission to Mars took 48204825.0 seconds
>>>
>>> print(f'Ares3 mission to Mars took {ares3:minutes}')
Ares3 mission to Mars took 803413.8 minutes
>>>
>>> print(f'Ares3 mission to Mars took {ares3:hours}')
Ares3 mission to Mars took 13390.2 hours
>>>
>>> print(f'Ares3 mission to Mars took {ares3:days}')
Ares3 mission to Mars took 557.9 days
>>>
>>> print(f'Ares3 mission to Mars took {ares3:months}')
Ares3 mission to Mars took 18.3 months
>>>
>>> print(f'Ares3 mission to Mars took {ares3:years}')
Ares3 mission to Mars took 1.5 years
10.3.11. Use Case - 0x04¶
>>> from dataclasses import dataclass
>>>
>>>
>>> SECOND = 1
>>> MINUTE = 60 * SECOND
>>> HOUR = 60 * MINUTE
>>> DAY = 24 * HOUR
>>> MONTH = 30.4375 * DAY
>>> YEAR = 365.25 * DAY
>>>
>>> SOL = 24*HOUR + 39*MINUTE + 35*SECOND
>>>
>>>
>>> @dataclass
... class Duration:
... seconds: int
...
... def __format__(self, unit):
... duration = self.seconds
... match unit:
... case 's' | 'seconds': duration /= SECOND
... case 'm' | 'minutes': duration /= MINUTE
... case 'h' | 'hours': duration /= HOUR
... case 'd' | 'days': duration /= DAY
... case 'M' | 'months': duration /= MONTH
... case 'y' | 'years': duration /= YEAR
... case _: raise TypeError('Invalid unit')
... return f'{duration:.1f} {unit}'
>>>
>>>
>>> ares3 = Duration(543*SOL)
>>>
>>> print(f'Ares3 mission to Mars took {ares3:seconds}')
Ares3 mission to Mars took 48204825.0 seconds
>>>
>>> print(f'Ares3 mission to Mars took {ares3:minutes}')
Ares3 mission to Mars took 803413.8 minutes
>>>
>>> print(f'Ares3 mission to Mars took {ares3:hours}')
Ares3 mission to Mars took 13390.2 hours
>>>
>>> print(f'Ares3 mission to Mars took {ares3:days}')
Ares3 mission to Mars took 557.9 days
>>>
>>> print(f'Ares3 mission to Mars took {ares3:months}')
Ares3 mission to Mars took 18.3 months
>>>
>>> print(f'Ares3 mission to Mars took {ares3:years}')
Ares3 mission to Mars took 1.5 years
10.3.12. Use Case - 0x05¶
Temperature conversion
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class Temperature:
... kelvin: float
...
... 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
10.3.13. Use Case - 0x05¶
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}
10.3.14. Assignments¶
"""
* Assignment: Operator String Str
* Required: yes
* Complexity: easy
* Lines of code: 3 lines
* Time: 5 min
English:
1. Overload `str()`
2. While printing object show: species name and a sum of `self.features`,
example: `label='setosa', total=9.4`
3. Result of sum round to one decimal place
4. Run doctests - all must succeed
Polish:
1. Przeciąż `str()`
2. Przy wypisywaniu obiektu pokaż: nazwę gatunku i sumę `self.features`,
przykład: `label='setosa', total=9.4`
3. Wynik sumowania zaokrąglij do jednego miejsca po przecinku
4. Uruchom doctesty - wszystkie muszą się powieść
Hints:
* f'{var=:.1f}'
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> for *features, label in DATA:
... iris = Iris(features, label)
... print(iris)
label='setosa', total=9.4
label='versicolor', total=16.3
label='virginica', total=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:
features: list
label: str
def __init__(self, features, label):
self.features = features
self.label = label
"""
* Assignment: Operator String 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])
"""
* Assignment: Operator String Format
* Required: yes
* Complexity: easy
* Lines of code: 8 lines
* Time: 8 min
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
>>> result = Distance(meters=1337)
>>> format(result, 'km')
'1.3'
>>> format(result, 'cm')
'133700.0'
>>> format(result, 'm')
'1337.0'
"""
METER = 1
CENTIMETER = METER * 0.01
KILOMETER = METER * 1000
class Distance:
meters: int
def __init__(self, meters):
self.meters = meters
"""
* Assignment: Operator String Nested
* Required: yes
* Complexity: medium
* Lines of code: 9 lines
* Time: 13 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('Pan 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:
Pan 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