3.4. OOP Interface¶
Python don't have interfaces
Cannot instantiate
Inheriting class must implement all methods
Only method declaration
Since Python 3.8: PEP 544 -- Protocols: Structural subtyping (static duck typing)
- interface¶
Software entity with public methods and attribute declaration
- implement¶
Class implements interface if has all public fields and methods from interface
>>> class CacheInterface:
... def set(self, key: str, value: str) -> None:
... raise NotImplementedError
...
... def get(self, key: str) -> str:
... raise NotImplementedError
...
... def is_valid(self, key: str) -> bool:
... raise NotImplementedError
3.4.1. Alternative Notation¶
>>> class CacheInterface:
... def set(self, key: str, value: str) -> None: raise NotImplementedError
... def get(self, key: str) -> str: raise NotImplementedError
... def is_valid(self, key: str) -> bool: raise NotImplementedError
Sometimes you may get a shorter code, but it will not raise an error.
>>> class CacheInterface:
... def set(self, key: str, value: str) -> None: pass
... def get(self, key: str) -> str: pass
... def is_valid(self, key: str) -> bool: pass
As of three dots (...
) is a valid Python object (Ellipsis) you can write that:
>>> class CacheInterface:
... def set(self, key: str, value: str) -> None: ...
... def get(self, key: str) -> str: ...
... def is_valid(self, key: str) -> bool: ...
The following code is not a valid Python syntax... How nice it would be to write:
>>> @interface
... class Cache:
... def set(self, key: str, value: str) -> None: ...
... def get(self, key: str) -> str: ...
... def is_valid(self, key: str) -> bool: ...
>>> class Cache(interface=True):
... def set(self, key: str, value: str) -> None: ...
... def get(self, key: str) -> str: ...
... def is_valid(self, key: str) -> bool: ...
>>> interface Cache:
... def set(self, key: str, value: str) -> None
... def get(self, key: str) -> str
... def is_valid(self, key: str) -> bool
3.4.2. Use Cases¶
>>> class Cache:
... 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 CacheDatabase(Cache):
... def is_valid(self, key: str) -> bool:
... ...
...
... def get(self, key: str) -> str:
... ...
...
... def set(self, key: str, value: str) -> None:
... ...
>>>
>>>
>>> class CacheRAM(Cache):
... def is_valid(self, key: str) -> bool:
... ...
...
... def get(self, key: str) -> str:
... ...
...
... def set(self, key: str, value: str) -> None:
... ...
>>>
>>>
>>> class CacheFilesystem(Cache):
... def is_valid(self, key: str) -> bool:
... ...
...
... def get(self, key: str) -> str:
... ...
...
... def set(self, key: str, value: str) -> None:
... ...
>>>
>>>
>>> fs: Cache = CacheFilesystem()
>>> fs.set('name', 'Mark Watney')
>>> fs.is_valid('name')
>>> fs.get('name')
>>>
>>> ram: Cache = CacheRAM()
>>> ram.set('name', 'Mark Watney')
>>> ram.is_valid('name')
>>> ram.get('name')
>>>
>>> db: Cache = CacheDatabase()
>>> db.set('name', 'Mark Watney')
>>> db.is_valid('name')
>>> db.get('name')
3.4.3. Assignments¶
"""
* Assignment: OOP Interface Define
* Complexity: easy
* Lines of code: 13 lines
* Time: 8 min
English:
1. Define interface `IrisInterface`
2. Attributes: `sepal_length, sepal_width, petal_length, petal_width`
3. Methods: `sum()`, `len()`, `mean()` in `IrisInterface`
4. All methods and constructor must raise exception `NotImplementedError`
5. Run doctests - all must succeed
Polish:
1. Zdefiniuj interfejs `IrisInterface`
2. Attributes: `sepal_length, sepal_width, petal_length, petal_width`
3. Metody: `sum()`, `len()`, `mean()` w `IrisInterface`
4. Wszystkie metody oraz konstruktor muszą podnosić wyjątek `NotImplementedError`
5. Uruchom doctesty - wszystkie muszą się powieść
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isfunction
>>> assert hasattr(IrisInterface, '__annotations__'), \
'IrisInterface has no field type annotations'
>>> assert hasattr(IrisInterface, 'mean'), \
'IrisInterface has no method .mean()'
>>> assert hasattr(IrisInterface, 'sum'), \
'IrisInterface has no method .sum()'
>>> assert hasattr(IrisInterface, 'len'), \
'IrisInterface has no method .len()'
>>> assert isfunction(IrisInterface.mean), \
'IrisInterface.mean() is not a method'
>>> assert isfunction(IrisInterface.sum), \
'IrisInterface.sum() is not a method'
>>> assert isfunction(IrisInterface.len), \
'IrisInterface.len() is not a method'
>>> IrisInterface.__annotations__ # doctest: +NORMALIZE_WHITESPACE
{'sepal_length': <class 'float'>,
'sepal_width': <class 'float'>,
'petal_length': <class 'float'>,
'petal_width': <class 'float'>}
>>> iris = IrisInterface(5.8, 2.7, 5.1, 1.9)
Traceback (most recent call last):
NotImplementedError
"""
"""
* Assignment: OOP Interface Implement
* Complexity: easy
* Lines of code: 12 lines
* Time: 8 min
English:
1. Define class `Setosa` implementing `IrisInterface`
2. Implement interface
3. Run doctests - all must succeed
Polish:
1. Stwórz klasę `Setosa` implementującą `IrisInterface`
2. Zaimplementuj interfejs
3. Uruchom doctesty - wszystkie muszą się powieść
Hints:
* `vars(self).values()`
* `mean = sum() / len()`
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isfunction
>>> assert issubclass(Setosa, IrisInterface)
>>> assert hasattr(Setosa, '__annotations__'), \
'Setosa has no field type annotations'
>>> assert hasattr(Setosa, 'mean'), \
'Setosa has no method .mean()'
>>> assert hasattr(Setosa, 'sum'), \
'Setosa has no method .sum()'
>>> assert hasattr(Setosa, 'len'), \
'Setosa has no method .len()'
>>> assert isfunction(Setosa.mean), \
'Setosa.mean() is not a method'
>>> assert isfunction(Setosa.sum), \
'Setosa.sum() is not a method'
>>> assert isfunction(Setosa.len), \
'Setosa.len() is not a method'
>>> Setosa.__annotations__ # doctest: +NORMALIZE_WHITESPACE
{'sepal_length': <class 'float'>,
'sepal_width': <class 'float'>,
'petal_length': <class 'float'>,
'petal_width': <class 'float'>}
>>> setosa = Setosa(5.1, 3.5, 1.4, 0.2)
>>> setosa.len()
4
>>> setosa.sum()
10.2
>>> setosa.mean()
2.55
"""
class IrisInterface:
sepal_length: float
sepal_width: float
petal_length: float
petal_width: float
def __init__(self,
sepal_length: float,
sepal_width: float,
petal_length: float,
petal_width: float) -> None:
raise NotImplementedError
def mean(self) -> float:
raise NotImplementedError
def sum(self) -> float:
raise NotImplementedError
def len(self) -> int:
raise NotImplementedError
"""
* Assignment: OOP Interface Protected
* Complexity: easy
* Lines of code: 12 lines
* Time: 8 min
English:
1. Define class `Setosa` implementing `IrisInterface`
2. Implement interface
3. Note, that attribute `species` is a `str`, and in Python you cannot add `str` and `float`
4. Create protected method `_get_values()` which returns values of `int` and `float` type attibutes
5. Why this method is not in interface?
6. Run doctests - all must succeed
Polish:
1. Stwórz klasę `Setosa` implementującą `IrisInterface`
2. Zaimplementuj interfejs
3. Zwróć uwagę, że atrybut `species` jest `str`, a Python nie można dodawać `str` i `float`
4. Stwórz metodę chronioną `_get_values()`, która zwraca wartości atrybutów typu `int` i `float`
5. Dlaczego ta metoda nie jest w interfejsie?
6. Uruchom doctesty - wszystkie muszą się powieść
Hints:
* `var(self).values()`
* `instanceof()` or `type()`
* `mean = sum() / len()`
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isfunction
>>> assert issubclass(Setosa, IrisInterface)
>>> assert hasattr(Setosa, '__annotations__'), \
'Setosa has no field type annotations'
>>> assert hasattr(Setosa, 'mean'), \
'Setosa has no method .mean()'
>>> assert hasattr(Setosa, 'sum'), \
'Setosa has no method .sum()'
>>> assert hasattr(Setosa, 'len'), \
'Setosa has no method .len()'
>>> assert isfunction(Setosa.mean), \
'Setosa.mean() is not a method'
>>> assert isfunction(Setosa.sum), \
'Setosa.sum() is not a method'
>>> assert isfunction(Setosa.len), \
'Setosa.len() is not a method'
>>> Setosa.__annotations__ # doctest: +NORMALIZE_WHITESPACE
{'sepal_length': <class 'float'>,
'sepal_width': <class 'float'>,
'petal_length': <class 'float'>,
'petal_width': <class 'float'>,
'species': <class 'str'>}
>>> setosa = Setosa(5.1, 3.5, 1.4, 0.2, 'setosa')
>>> setosa.len()
4
>>> setosa.sum()
10.2
>>> setosa.mean()
2.55
"""
class IrisInterface:
sepal_length: float
sepal_width: float
petal_length: float
petal_width: float
species: str
def __init__(self,
sepal_length: float,
sepal_width: float,
petal_length: float,
petal_width: float,
species: str) -> None:
raise NotImplementedError
def mean(self) -> float:
raise NotImplementedError
def sum(self) -> float:
raise NotImplementedError
def len(self) -> int:
raise NotImplementedError