About Python Protocol
I have looked at the protocols as part of the sensor refactoring, but it doesn't seem appropriate, so I won't spend any more time on it at the moment.
From https://peps.python.org/pep-0544/#nominal-vs-structural-subtyping and a few quick tests, what I get from the protocols is that it's designed to do only typing. It is an overlay to ABC classes because in the protocol signature all functions may not be implemented only declared, but some behaviour seem unavailable.
An short example with Protocol (freely inspired by the link above)
class Sensor(Protocol):
name: str # This is a protocol member
resolution: int = 0 # This one too (with default)
def method(self) -> None:
self.temp: List[int] = [] # Error in type checker
def get_resolution(self) -> None: # method shared by all sensors
print(self.resolution)
class Sentinel1:
def __init__(self, name, resolution):
self.name = name
self.resolution = resolution
class Sentinel2:
def __init__(self, name: str, resolution: int) -> None:
self.name = name
self.resolution = resolution
def method(self) -> None:
return
def get_resolution(self) -> None:
print(self.resolution * 10)
The few lines below will leads to error in mypy
var: Sensor = Sentinel2('S2', 10)
var.get_resolution()
var2: Sensor = Sentinel1('S2', 10) # error with mypy
var2.get_resolution()
error: Incompatible types in assignment (expression has type "Sentinel1", variable has type "Sensor")
note: "Sentinel1" is missing following "Sensor" protocol members:
note: get_resolution, method
The execution raise also exeption:
100 # var.get_resolution()
Traceback (most recent call last):
File "/home/btardy/Documents/tmp/classes.py", line 94, in <module>
var2.get_resolution()
AttributeError: 'Sentinel1' object has no attribute 'get_resolution'
Then as Sentinel1 is not implicitly recognized as a Sensor Protocol it seems correct.
Lets force the inheritance for Sentinel1:
class Sentinel1(Sensor):
def __init__(self, name, resolution):
super().__init__(name, resolution)
mypy is still grumpy:
error: Cannot instantiate abstract class "Sentinel1" with abstract attribute "name"
but the execution run
100 # var.get_resolution()
0 # var2.get_resolution()
To solve the mypy error, it is required to add a __init__
to Sensor class. But it still return 0 which is the default value of Sensor instead of 10 the value passed in arguments.
Then by modifying Sentinel1
like for the Sentinel2 class, mypy is happy and the execution provide expected results.
However, it seems possible to add as many methods as you like to the Sentinel2 class without breaking the automatic protocol detection.
To summarize:
We could ensure that a sensor has the signature functions we expect but it stops there because there are no constraints at runtime, only when passing mypy.
Implicit inheritance, which is one of the strengths of the protocols, forces each subclass to define all methods with the correct types. Otherwise it does not recognise the class as inheriting the protocol.
Furthermore, it is not possible to code a function in the Protocol class and access it in the inheriting classes, confirmed here.
As the aim of refactoring sensors is reduce similar code, I'm not sure that it is adapted.
Looking at simple ABC class:
class Sentinel(ABC):
name: str
resolution: str
def __init__(self, name, resolution):
self.name = name
self.resolution = resolution
def method(self) -> None:
self.temp: List[int] = [] # Error in type checker
def get_resolution(self) -> None:
print(self.resolution)
class Sentinel2(Sentinel):
def __init__(self, name, resolution):
super().__init__(name, resolution)
var3: Sentinel = Sentinel2("S21", 20)
var3.get_resolution()
Seems good to mypy and the execution is correct.
If we change the function signature for get_resolution
in Sentinel2 class we got the following from mypy:
def get_resolution(self, factor: int):
print(self.resolution * factor)
error: Signature of "get_resolution" incompatible with supertype "Sentinel"
note: Superclass:
note: def get_resolution(self) -> None
note: Subclass:
note: def get_resolution(self, factor: int) -> Any
error: Too many arguments for "get_resolution" of "Sentinel"
So I don't really see the point of the protocols at this stage. Perhaps we should explore this further. It doesn't seem to me to be suitable for sensors.
I may have missed some things but I have the impression that it is designed for services.
For example a protocol that takes two float inputs and a compute method and where each class inheriting this protocol will compute all sorts of binary operations: addition, multiplication, etc... And the protocol will simply ensure that these classes can be typed by the parent class.