How to test that a python class does not subclass a protocol
Question:
I need to make sure that developers on my team are not directly subclassing a typing.Protocol
instance. That is, although their classes should implement the interface of the Protocol, we don’t want:
class MyClass(MyProtocol):
# inherits MyProtocol
which would coerce the class to be a subclass of the Protocol and, therefore, necessarily pass isinstance(MyClass(), MyProtocol)
even if it does not implement any logic in the attributes and methods of the class.
Example of the problem:
class MyProtocol(Protocol):
def foo(self):
raise NotImplementedError("Don't subclass MyProtocol!")
class MyClass(MyProtocol):
pass
# MyClass now has foo since it inherits the Protocol...
assert hasattr(MyClass(), 'foo')
In other words, I would like a test of the form:
def does_not_subclass_protocol(obj, protocol):
# returns True if and only if obj does not have
# MyClass(MyProtocol)...
# syntax in class definition.
Answers:
You can prohibit subclassing in the Protocol and use typing.runtime_checkable
:
from typing import Protocol, runtime_checkable
@runtime_checkable
class MyProtocol(Protocol):
def foo(self): ...
def __init_subclass__(cls, **kwargs):
raise TypeError
I don’t know whether the type checker will ignore the magic method __init_subclass__
because I can only test on my mobile phone now.
Direct implementation Protocol:
>>> class MyClass:
... def foo(self):
... print('hello')
...
>>> isinstance(MyClass(), MyProtocol)
True
Inheritance agreement:
>>> class MyClass(MyProtocol): pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.9/abc.py", line 106, in __new__
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
File "<stdin>", line 6, in __init_subclass__
TypeError
I need to make sure that developers on my team are not directly subclassing a typing.Protocol
instance. That is, although their classes should implement the interface of the Protocol, we don’t want:
class MyClass(MyProtocol):
# inherits MyProtocol
which would coerce the class to be a subclass of the Protocol and, therefore, necessarily pass isinstance(MyClass(), MyProtocol)
even if it does not implement any logic in the attributes and methods of the class.
Example of the problem:
class MyProtocol(Protocol):
def foo(self):
raise NotImplementedError("Don't subclass MyProtocol!")
class MyClass(MyProtocol):
pass
# MyClass now has foo since it inherits the Protocol...
assert hasattr(MyClass(), 'foo')
In other words, I would like a test of the form:
def does_not_subclass_protocol(obj, protocol):
# returns True if and only if obj does not have
# MyClass(MyProtocol)...
# syntax in class definition.
You can prohibit subclassing in the Protocol and use typing.runtime_checkable
:
from typing import Protocol, runtime_checkable
@runtime_checkable
class MyProtocol(Protocol):
def foo(self): ...
def __init_subclass__(cls, **kwargs):
raise TypeError
I don’t know whether the type checker will ignore the magic method __init_subclass__
because I can only test on my mobile phone now.
Direct implementation Protocol:
>>> class MyClass:
... def foo(self):
... print('hello')
...
>>> isinstance(MyClass(), MyProtocol)
True
Inheritance agreement:
>>> class MyClass(MyProtocol): pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.9/abc.py", line 106, in __new__
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
File "<stdin>", line 6, in __init_subclass__
TypeError