6.9. Type Annotation Types¶
All classes are types
Since 3.11: PEP 673 - Self Type
Since 3.7:
from __future__ import annotations
>>> class Point:
... x: int
... y: int
...
... 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)
6.9.1. Dynamic Attributes¶
>>> class User:
... username: str
... password: str
>>> mark = User()
>>> mark.username
Traceback (most recent call last):
AttributeError: 'User' object has no attribute 'username'
>>> mark = User()
>>> mark.username = 'mwatney'
>>> mark.username
'mwatney'
6.9.2. Static Attributes¶
ClassVar indicates that a given attribute is intended to be used as a class variable and should not be set on instances of that class.
https://docs.python.org/3/library/typing.html#typing.ClassVar
Import:
>>> from typing import ClassVar
Example:
>>> class User:
... AGE_MIN: ClassVar[int] = 30
... AGE_MAX: ClassVar[int] = 50
6.9.3. Method Return Type¶
>>> class User:
... def say_hello(self) -> str:
... return 'My name... José Jiménez'
6.9.4. Required Method Arguments¶
>>> class User:
... def say_hello(self, name: str) -> str:
... return f'My name... {name}'
6.9.5. Optional Method Arguments¶
>>> class User:
... def say_hello(self, name: str = 'Mark Watney') -> str:
... return f'My name... {name}'
6.9.6. Init Method¶
>>> class User:
... firstname: str
... lastname: str
...
... def __init__(self, firstname: str, lastname: str) -> None:
... self.firstname = firstname
... self.lastname = lastname
6.9.7. Composition¶
>>> class User:
... firstname: str
... lastname: str
>>>
>>>
>>> class Admin:
... firstname: str
... lastname: str
... friends: User
6.9.8. Aggregation¶
>>> class User:
... firstname: str
... lastname: str
>>>
>>>
>>> class Admin:
... firstname: str
... lastname: str
... friends: list[User]
6.9.9. Self¶
>>> class User:
... firstname: str
... lastname: str
... friends: list[User]
...
Traceback (most recent call last):
NameError: name 'User' is not defined
>>> class User:
... firstname: str
... lastname: str
... friends: list['User']
>>> class User:
... firstname: str
... lastname: str
... friends: 'list[User]'
>>> class User:
... firstname: 'str'
... lastname: 'str'
... friends: 'list[User]'
Since Python 3.7:
>>> from __future__ import annotations
>>>
>>>
>>> class User:
... firstname: str
... lastname: str
... friends: list[User]
Since 3.11: PEP 673 - Self Type
>>> from typing import Self
>>>
>>>
>>> class User:
... firstname: str
... lastname: str
... friends: list[Self]
What's the difference?
>>> class User:
... firstname: str
... lastname: str
>>>
>>> User.__annotations__
{'firstname': <class 'str'>, 'lastname': <class 'str'>}
>>> from __future__ import annotations
>>>
>>> class User:
... firstname: str
... lastname: str
...
>>>
>>> User.__annotations__
{'firstname': 'str', 'lastname': 'str'}
6.9.10. Instance¶
>>> class Astronaut:
... pass
>>>
>>>
>>> mark: Astronaut = Astronaut()
>>> melissa: Astronaut = Astronaut()
6.9.11. Dependency Inversion Principle¶
Always depend upon abstraction not an implementation
More information in OOP SOLID
>>> class Person:
... pass
>>>
>>> class Astronaut(Person):
... pass
>>>
>>> class Cosmonaut(Person):
... pass
>>>
>>>
>>> mark: Person = Astronaut()
>>> melissa: Person = Cosmonaut()
6.9.12. Final Class¶
Since Python 3.8: PEP 591 -- Adding a final qualifier to typing
There is no runtime checking of these properties
The following code demonstrates how to use @final
decorator to mark
class as final:
>>> from typing import final
>>>
>>>
>>> @final
... class Astronaut:
... pass
6.9.13. Final Method¶
Since Python 3.8: PEP 591 -- Adding a final qualifier to typing
There is no runtime checking of these properties
The following code demonstrates how to use @final
decorator to mark
method as final:
>>> from typing import final
>>>
>>>
>>> class Astronaut:
... @final
... def say_hello(self) -> None:
... pass
6.9.14. Final Attribute¶
A special typing construct to indicate to type checkers that a name cannot be re-assigned or overridden in a subclass
There is no runtime checking of these properties
The following code demonstrates how to use Final
class to mark
attribute as final:
>>> from typing import Final
>>>
>>>
>>> class Astronaut:
... firstname: Final[str]
... lastname: Final[str]
...
... def __init__(self) -> None:
... self.firstname = 'Mark'
... self.lastname = 'Watney'
6.9.15. Errors¶
Error: 'Astronaut' is marked as @final
and should not be subclassed:
>>> from typing import final
>>>
>>>
>>> @final
... class Person:
... pass
>>>
>>> class Astronaut(Person):
... pass
The following code will yield with an error: 'Person.say_hello' is marked
as @final
and should not be overridden:
>>> from typing import final
>>>
>>>
>>> class Person:
... @final
... def say_hello(self) -> None:
... pass
>>>
>>> class Astronaut(Person):
... def say_hello(self) -> None:
... pass
The following code will yield with an error: final attribute (y
) without
an initializer:
>>> from typing import Final
>>>
>>>
>>> class Astronaut:
... firstname: Final[str]
... lastname: Final[str] # error: not initialized
...
... def __init__(self) -> None:
... self.firstname = 'Mark'
The following code will yield with an error: can't override a final attribute:
>>> from typing import Final
>>>
>>>
>>> class Astronaut:
... AGE_MIN: Final[int] = 30
... AGE_MAX: Final[int] = 50
>>>
>>>
>>> Astronaut.AGE_MAX = 65 # error: can't override
The following code will yield with an error: can't override a final attribute:
>>> from typing import Final
>>>
>>>
>>> class Astronaut:
... AGE_MIN: Final[int] = 30
... AGE_MAX: Final[int] = 50
>>>
>>>
>>> class VeteranAstronaut(Astronaut):
... AGE_MAX = 65 # error: can't override
6.9.16. Use Case - 0x01¶
>>> class Astronaut:
... def get_name(self) -> tuple[str, str]:
... return 'Mark', 'Watney'
6.9.17. Use Case - 0x02¶
SOLID Dependency Inversion Principle
>>> class ICache:
... pass
>>>
>>> class DatabaseCache(ICache):
... pass
>>>
>>> class LocmemCache(ICache):
... pass
>>>
>>> class FilesystemCache(ICache):
... pass
>>>
>>>
>>> db: ICache = DatabaseCache()
>>> mem: ICache = LocmemCache()
>>> fs: ICache = FilesystemCache()
>>> class ICache:
... def get(self, key: str) -> str: raise NotImplementedError
... def set(self, key: str, value: str) -> None: raise NotImplementedError
... def is_valid(self, key: str) -> bool: raise NotImplementedError
>>>
>>>
>>> class DatabaseCache(ICache):
... def get(self, key: str) -> str:
... pass
...
... def set(self, key: str, value: str) -> None:
... pass
...
... def is_valid(self, key: str) -> bool:
... pass
>>>
>>>
>>> class FilesystemCache(ICache):
... def get(self, key: str) -> str:
... pass
...
... def set(self, key: str, value: str) -> None:
... pass
...
... def is_valid(self, key: str) -> bool:
... pass
>>>
>>>
>>> class LocmemCache(ICache):
... def get(self, key: str) -> str:
... pass
...
... def set(self, key: str, value: str) -> None:
... pass
...
... def is_valid(self, key: str) -> bool:
... pass
>>>
>>>
>>> mycache: ICache = FilesystemCache()
>>> mycache.set('firstname', 'Mark')
>>> mycache.is_valid('firstname')
>>> mycache.get('firstname')
6.9.18. Use Case - 0x03¶
>>> class Point:
... x: int
... y: int
...
... 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)
6.9.19. Use Case - 0x04¶
>>> class Point:
... 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:
... def __init__(self, initial_position: Point = Point()) -> None:
... self.position = initial_position
...
... def get_coordinates(self) -> Point:
... return self.position
>>>
>>>
>>> pos: Position = Position()
>>>
>>> pos.get_coordinates()
<__main__.Point object at 0x...>
>>>
>>> print(pos.get_coordinates())
Point(x=0, y=0)
6.9.20. Use Case - 0x05¶
>>> class Iris:
... def __init__(self, features: list[float], label: str) -> None:
... self.features: list[float] = features
... self.label: str = 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')]
6.9.21. Use Case - 0x06¶
Immutable attributes (set only on init)
>>> from typing import Final
>>> class Position:
... x: Final[int]
... y: Final[int]
...
... def __init__(self) -> None:
... self.x = 1
... self.y = 2
>>> class Position:
... x: Final[int]
... y: Final[int]
...
... def __init__(self, x: int, y: int) -> None:
... self.x = x
... self.y = y
6.9.22. Use Case - 0x07¶
>>> 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
6.9.23. Use Case - 0x08¶
>>> from typing import Final
>>>
>>>
>>> class Hero:
... DAMAGE_MIN: Final[int] = 10
... DAMAGE_MAX: Final[int] = 20
6.9.24. Further Reading¶
More information in Type Annotations
More information in CI/CD Type Checking