5.14. OOP Abstract 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
5.14.1. Syntax¶
Names:
Cache
,CacheInterface
,ICache
,CacheIface
>>> 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
5.14.2. 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
5.14.3. Example¶
>>> class ICache:
... def set(self, key: str, value: str) -> None: ...
... def get(self, key: str) -> str: ...
... def is_valid(self, key: str) -> bool: ...
>>>
>>>
>>> class DatabaseCache(ICache):
... ...
>>>
>>> class InMemoryCache(ICache):
... ...
>>>
>>> class FilesystemCache(ICache):
... ...
>>>
>>>
>>> c: ICache = DatabaseCache()
>>> c.set('firstname', 'Mark')
>>> c.is_valid('firstname')
>>> c.is_valid('lastname')
>>> c.get('firstname')
>>>
>>> c: ICache = InMemoryCache()
>>> c.set('firstname', 'Mark')
>>> c.is_valid('firstname')
>>> c.is_valid('lastname')
>>> c.get('firstname')
>>>
>>> c: ICache = FilesystemCache()
>>> c.set('firstname', 'Mark')
>>> c.is_valid('firstname')
>>> c.is_valid('lastname')
>>> c.get('firstname')
5.14.4. Use Case - 0x01¶
Cache
File cache_interface.py
:
>>> 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
File cache_impl.py
:
>>> class CacheDatabase(ICache):
... def is_valid(self, key: str) -> bool:
... ...
...
... def get(self, key: str) -> str:
... ...
...
... def set(self, key: str, value: str) -> None:
... ...
>>>
>>>
>>> class CacheRAM(ICache):
... def is_valid(self, key: str) -> bool:
... ...
...
... def get(self, key: str) -> str:
... ...
...
... def set(self, key: str, value: str) -> None:
... ...
>>>
>>>
>>> class CacheFilesystem(ICache):
... def is_valid(self, key: str) -> bool:
... ...
...
... def get(self, key: str) -> str:
... ...
...
... def set(self, key: str, value: str) -> None:
... ...
File settings.py
>>> from myapp.cache_interface import ICache
>>> from myapp.cache_imp import DatabaseCache
>>> from myapp.cache_imp import InMemoryCache
>>> from myapp.cache_imp import FilesystemCache
>>>
>>>
>>> DefaultCache = InMemoryCache
File myapp.py
:
>>> from myapp.settings import DefaultCache, ICache
>>> cache: ICache = DefaultCache()
>>> cache.set('name', 'Mark Watney')
>>> cache.is_valid('name')
>>> cache.get('name')
5.14.5. Use Case - 0x02¶
>>> class Tool:
... def on_mouse_over(self): raise NotImplementedError
... def on_mouse_out(self): raise NotImplementedError
... def on_mouse_click_leftbutton(self): raise NotImplementedError
... def on_mouse_unclick_leftbutton(self): raise NotImplementedError
... def on_mouse_click_rightbutton(self): raise NotImplementedError
... def on_mouse_unclick_rightbutton(self): raise NotImplementedError
... def on_key_press(self): raise NotImplementedError
... def on_key_unpress(self): raise NotImplementedError
>>>
>>>
>>> class Pencil(Tool):
... def on_mouse_over(self):
... ...
...
... def on_mouse_out(self):
... ...
...
... def on_mouse_click_leftbutton(self):
... ...
...
... def on_mouse_unclick_leftbutton(self):
... ...
...
... def on_mouse_click_rightbutton(self):
... ...
...
... def on_mouse_unclick_rightbutton(self):
... ...
...
... def on_key_press(self):
... ...
...
... def on_key_unpress(self):
... ...
>>>
>>>
>>> class Pen(Tool):
... def on_mouse_over(self):
... ...
...
... def on_mouse_out(self):
... ...
...
... def on_mouse_click_leftbutton(self):
... ...
...
... def on_mouse_unclick_leftbutton(self):
... ...
...
... def on_mouse_click_rightbutton(self):
... ...
...
... def on_mouse_unclick_rightbutton(self):
... ...
...
... def on_key_press(self):
... ...
...
... def on_key_unpress(self):
... ...
5.14.6. 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, 'mean')
>>> assert hasattr(IrisInterface, 'sum')
>>> assert hasattr(IrisInterface, 'len')
>>> assert isfunction(IrisInterface.mean)
>>> assert isfunction(IrisInterface.sum)
>>> assert isfunction(IrisInterface.len)
>>> 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: 5 min
English:
1. Define class `Setosa` implementing `IrisInterface`
2. Implement methods
3. Run doctests - all must succeed
Polish:
1. Stwórz klasę `Setosa` implementującą `IrisInterface`
2. Zaimplementuj metory
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, 'mean')
>>> assert hasattr(Setosa, 'sum')
>>> assert hasattr(Setosa, 'len')
>>> assert isfunction(Setosa.mean)
>>> assert isfunction(Setosa.sum)
>>> assert isfunction(Setosa.len)
>>> 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 Values
* Complexity: easy
* Lines of code: 12 lines
* Time: 8 min
English:
1. Define class `Setosa` implementing `IrisInterface`
2. Implement methods
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 metody
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, 'mean')
>>> assert hasattr(Setosa, 'sum')
>>> assert hasattr(Setosa, 'len')
>>> assert isfunction(Setosa.mean)
>>> assert isfunction(Setosa.sum)
>>> assert isfunction(Setosa.len)
>>> 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