3.10. Typing Type
All classes are types
>>> class Account:
... firstname: str
... lastname: str
...
... def __init__(self, firstname: str, lastname: str) -> None:
... self.firstname = firstname
... self.lastname = lastname
...
... def login(self, username: str, password: str | None = None) -> None:
... ...
...
... def logout(self) -> None:
... ...
>>>
>>>
>>> class User(Account):
... pass
>>>
>>> class Admin(Account):
... pass
>>>
>>>
>>> mark: Account = User('Mark', 'Watney')
>>> melissa: Account = Admin('Melissa', 'Lewis')
3.10.1. Instance
>>> class User:
... pass
>>>
>>>
>>> a: User = User()
>>> b: User = User()
3.10.2. Inheritance
Dependency Inversion Principle
Always depend upon abstraction not an implementation
More information in OOP SOLID
>>> class Account:
... pass
>>>
>>> class User(Account):
... pass
>>>
>>> class Admin(Account):
... pass
>>>
>>>
>>> a: Account = User()
>>> b: Account = Admin()
3.10.3. Instance Variables
>>> class User:
... firstname: str
... lastname: str
>>>
>>>
>>> mark = User()
>>> mark.firstname = 'Mark'
>>> mark.lastname = 'Watney'
3.10.4. Class Variables
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
>>> from typing import ClassVar
>>>
>>> class User:
... AGE_MIN: ClassVar[int] = 18
... AGE_MAX: ClassVar[int] = 65
3.10.5. Method Return Type
>>> class User:
... def login(self) -> None:
... ...
3.10.6. Required Method Arguments
>>> class User:
... def login(self, username: str, password: str):
... ...
3.10.7. Optional Method Arguments
>>> class User:
... def login(self, username: str, password: str | None = None):
... ...
3.10.8. Init Method
>>> class User:
... firstname: str
... lastname: str
...
... def __init__(self, firstname: str, lastname: str) -> None:
... self.firstname = firstname
... self.lastname = lastname
3.10.9. Composition
>>> class Group:
... name: str
>>>
>>> class User:
... firstname: str
... lastname: str
... group: Group
3.10.10. Aggregation
>>> class Group:
... name: str
>>>
>>> class User:
... firstname: str
... lastname: str
... group: list[Group]
3.10.11. Forward Reference Problem
>>> class User:
... firstname: str
... lastname: str
... friends: list[User]
...
Traceback (most recent call last):
NameError: name 'User' is not defined
3.10.12. Forward Reference Solution: Str
Annotations can be a str
>>> 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]'
3.10.13. Forward Reference Solution: Future Annotations
Since Python 3.7
from __future__ import annotations
>>> from __future__ import annotations
>>>
>>> class User:
... firstname: str
... lastname: str
... friends: list[User]
>>> User.__annotations__
{'firstname': <class 'str'>, 'lastname': <class 'str'>, 'friends': list[__main__.User]}
3.10.14. Forward Reference Solution: Self
Since 3.11: PEP 673 - Self Type
>>> from typing import Self
>>>
>>> class User:
... firstname: str
... lastname: str
... friends: list[Self]
>>> User.__annotations__
{'firstname': <class 'str'>, 'lastname': <class 'str'>, 'friends': list[typing.Self]}
3.10.15. 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 User:
... pass
>>>
>>> class Admin(User): # error
... pass
The code above will yield with an error: 'User' is marked as '@final' and should not be subclassed.
3.10.16. 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 User:
... @final
... def login(self):
... pass
>>>
>>> class Admin(User):
... def login(self): # error
... pass
The code above will yield with an error: 'User.login' is marked as '@final' and should not be overridden:
3.10.17. 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 User:
... username: Final[str]
... password: str
...
... def __init__(self, username, password):
... self.username = username
... self.password = password
...
... def change_password(self, new_password):
... self.password = new_password
The following code will yield with an error: 'Final' name should be initialized with a value:
>>> class User:
... username: Final[str]
... password: str
3.10.18. Override
Method need to be overriden in child class
>>> from typing import override
>>>
>>>
>>> class Account:
... @override
... def login(self):
... ...
>>>
>>> class User(Account):
... def login(self):
... print('ok')
3.10.19. Overload
Signs that a method is overloaded on purpose
It is not a mistake, that it has the same name as in base class
>>> from typing import overload
>>>
>>>
>>> class Account:
... def login(self):
... ...
>>>
>>> class User(Account):
... @overload
... def login(self):
... print('ok')
3.10.20. Override and Overload
>>> class Account:
... @override
... def login(self):
... ...
>>>
>>> class User(Account):
... @overload
... def login(self):
... print('ok')
3.10.21. Recap
Type annotations
Type hints
Optional, but a good practice
Python does not verify if values have correct types
IDEs such as PyCharm, VS Code or tools such as
mypy
can do that
No Attribute Definition:
>>> class User:
... pass
Basic Types:
>>> class User:
... firstname: str
... lastname: str
... age: int
Union:
>>> class User:
... firstname: str
... lastname: str
... age: int | float
Optional:
>>> class User:
... firstname: str
... lastname: str
... age: int | float
... height: float | None
... weight: float | None
Sequences:
>>> class User:
... firstname: str
... lastname: str
... age: int
... groups: list[str]
Relation One to One:
>>> class Group:
... gid: int
... name: int
>>>
>>>
>>> class User:
... firstname: str
... lastname: str
... group: Group
Relation One to Many:
>>> class Group:
... gid: int
... name: str
>>>
>>>
>>> class User:
... firstname: str
... lastname: str
... groups: list[Group]
3.10.22. Use Case - 1
>>> class Point:
... x: int
... y: int
... z: int
3.10.23. Use Case - 2
>>> class Date:
... year: int
... month: int
... day: int
3.10.24. Use Case - 3
>>> class Laptop:
... cpu: str
... ram: str
... ssd: str
3.10.25. Use Case - 4
>>> class Iris:
... features: list[float]
... label: str
3.10.26. Use Case - 5
>>> class Iris:
... sepal_length: float
... sepal_width: float
... petal_length: float
... petal_width: float
... species: str
3.10.27. Use Case - 6
>>> class User:
... firstname: str
... lastname: str
... email: str
... active: bool
... age: int | float
... height: float | None
... weight: float | None
... groups: list[str] | None
... friends: list['User'] | None
3.10.28. Use Case - 7
>>> from datetime import date
>>> from typing import Literal
>>>
>>>
>>> class Address:
... type: Literal['home', 'work']
... street: str
... house: str
... apartment: str
... post_code: str
... city: str
... region: str
... country: str
>>>
>>>
>>> class PhoneNumber:
... type: Literal['home', 'work', 'mobile']
... number: str
>>>
>>>
>>> class User:
... firstname: str
... lastname: str
... age: int | float
... birthdate: date
... gender: Literal['male', 'female']
... height: float | None
... weight: float | None
... education: list[str] | None
... job: str | None
... addresses: list[Address] | None
... emails: list[str] | None
... phones: PhoneNumber | None
... friends: list['Person'] | None
3.10.29. Use Case - 8
>>> 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
3.10.30. Use Case - 9
>>> class Point:
... x: int
... y: int
...
... def __init__(self, x: int = 0, y: int = 0) -> None:
... self.x = x
... self.y = 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
3.10.31. Use Case - 10
SOLID Dependency Inversion Principle
>>> class Cache:
... pass
>>>
>>> class DatabaseCache(Cache):
... pass
>>>
>>> class MemoryCache(Cache):
... pass
>>>
>>> class FilesystemCache(Cache):
... pass
>>>
>>>
>>> a: Cache = DatabaseCache()
>>> b: Cache = MemoryCache()
>>> c: Cache = FilesystemCache()
3.10.32. Use Case - 11
>>> 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'),
... ]
3.10.33. Use Case - 12
>>> from typing import ClassVar
>>>
>>> class Settings:
... RESOLUTION_X_MIN: ClassVar[int] = 0
... RESOLUTION_X_MAX: ClassVar[int] = 1024
... RESOLUTION_Y_MIN: ClassVar[int] = 0
... RESOLUTION_Y_MAX: ClassVar[int] = 768
3.10.34. Use Case - 13
>>> from typing import ClassVar
>>>
>>>
>>> class Position:
... x: int
... y: int
>>>
>>>
>>> class Hero:
... HEALTH_MIN: ClassVar[int] = 50
... HEALTH_MAX: ClassVar[int] = 100
... GOLD_MIN: ClassVar[int] = 50
... GOLD_MAX: ClassVar[int] = 100
... DAMAGE_MIN: ClassVar[int] = 5
... DAMAGE_MAX: ClassVar[int] = 20
... name: str
... health: int
... gold: int
... position: Position