2.5. Annotation OOP¶
2.5.1. Rationale¶
All classes are types
Always depend upon abstraction not an implementation (SOLID Dependency Inversion Principle)
More information in OOP SOLID
class Astronaut:
pass
mark: Astronaut = Astronaut()
class Cache:
pass
class DatabaseCache(Cache):
pass
class MemoryCache(Cache):
pass
class FilesystemCache(Cache):
pass
db: Cache = DatabaseCache()
mem: Cache = MemoryCache()
fs: Cache = FilesystemCache()
2.5.2. Attributes¶
class Point:
x: int
y: int
class Astronaut:
firstname: str
lastname: str
Warning! - Do not confuse this with static fields:
class Astronaut: firstname: str = 'Mark' lastname: str = 'Watney'
2.5.3. Method Return Types¶
class Astronaut:
def say_hello(self) -> str:
return 'My name... José Jiménez'
mark: Astronaut = Astronaut()
mark.say_hello()
# 'My name... José Jiménez'
class Point:
def get_coordinates(self) -> tuple[int, int]:
return 1, 2
pt: Point = Point()
pt.get_coordinates()
# (1, 2)
2.5.4. Required Method Arguments¶
class Point:
x: int
y: int
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
class Astronaut:
firstname: str
lastname: str
def __init__(self, firstname: str, lastname: str) -> None:
self.firstname: str = firstname
self.lastname: str = lastname
2.5.5. Optional Method Arguments¶
class Point:
x: int
y: int
def __init__(self, x: int = 0, y: int = 0) -> None:
self.x = x
self.y = y
def set_coordinates(self, x: int, y: int) -> None:
self.x = x
self.y = y
def get_coordinates(self) -> tuple[int, int]:
return self.x, self.y
pt: Point = Point()
pt.set_coordinates(1, 2)
pt.get_coordinates()
# (1, 2)
class Point:
x: int
y: int
def __init__(self, x: int = 0, y: int = 0) -> None:
self.x = x
self.y = y
def __str__(self) -> str:
return f'Point(x={self.x}, y={self.y})'
2.5.6. Classes as Method Arguments¶
class Point:
x: int
y: int
def __init__(self, x: int = 0, y: int = 0) -> None:
self.x = x
self.y = y
def __str__(self) -> str:
return f'Point(x={self.x}, y={self.y})'
class Position:
position: Point
def __init__(self, initial_position: Point = Point()) -> None:
self.position = initial_position
def get_coordinates(self) -> Point:
return self.position
pos: Position = Position()
print(pos.get_coordinates())
# Point(x=0, y=0)
2.5.7. Aggregation¶
class Mission:
year: int
name: str
class Astronaut:
firstname: str
lastname: str
mission: list[Mission]
2.5.8. Nested¶
class Iris:
features: list[float]
label: str
def __init__(self, features: list[float], label: str) -> None:
self.features = features
self.label = label
data: list[Iris] = [
Iris([4.7, 3.2, 1.3, 0.2], 'setosa'),
Iris([7.0, 3.2, 4.7, 1.4], 'versicolor'),
Iris([7.6, 3.0, 6.6, 2.1], 'virginica'),
]
2.5.9. Final Class¶
Since Python 3.8: PEP 591 -- Adding a final qualifier to typing
from typing import final
@final
class Astronaut:
pass
Error: Cannot inherit from final class "Base":
from typing import final
@final
class Astronaut:
pass
class Pilot(Astronaut):
pass
2.5.10. Final Method¶
Since Python 3.8: PEP 591 -- Adding a final qualifier to typing
from typing import final
class Astronaut:
@final
def say_hello(self) -> None:
pass
Error: Cannot override final attribute "foo" (previously declared in base class "Base"):
from typing import final
class Astronaut:
@final
def say_hello(self) -> None:
pass
class Pilot(Astronaut):
def say_hello(self) -> None: # Error: Cannot override final attribute
pass
2.5.11. Final Attribute¶
from typing import Final
class Hero:
DAMAGE_MIN: Final[int] = 10
DAMAGE_MAX: Final[int] = 20
Hero.DAMAGE_MIN = 1 # Error: can't override a final attribute
Error: can't override a final attribute:
from typing import Final
class Position:
x: Final[int]
y: Final[int]
def __init__(self) -> None:
self.x = 1
self.y = 2
Error: final attribute (y
) without an initializer:
from typing import Final
class Position:
x: Final[int]
y: Final[int] # Error: final attribute 'y' without an initializer
def __init__(self) -> None:
self.x = 1
Error: can't override a final attribute:
from typing import Final
class Settings:
RESOLUTION_X_MIN: Final[int] = 0
RESOLUTION_X_MAX: Final[int] = 1024
RESOLUTION_Y_MIN: Final[int] = 0
RESOLUTION_Y_MAX: Final[int] = 768
class Game(Settings):
RESOLUTION_X_MIN = 3 # Error: can't override a final attribute
2.5.12. Forward References¶
class Astronaut:
firstname: str
lastname: str
friends: list[Astronaut] # Error, cannot reference to class which is still being defined
class Astronaut:
firstname: str
lastname: str
friends: list['Astronaut']
Since Python 3.10: PEP 563 -- Postponed Evaluation of Annotations
class Astronaut:
firstname: str
lastname: str
friends: list[Astronaut] # This code will work only in Python 3.10
In Python 3.7, 3.8 and 3.9 you can get this behavior by importing it from __future__
:
from __future__ import annotations
class Astronaut:
firstname: str
lastname: str
friends: list[Astronaut]
2.5.13. Further Reading¶
What’s New In Python 3.10: https://docs.python.org/dev/whatsnew/3.10.html
More information in Type Annotations
More information in CI/CD Type Checking