So my problem is That when I have a class of type A
that does things and I use those functions as a subclass(B)
they are still typed for class A
and do not accept my class B
object as arguments or as function signature.
My problem simplified:
from typing import TypeVar, Generic, Callable
T = TypeVar('T')
class Signal(Generic[T]):
def connect(self, connector: Callable[[T], None]) -> None:
pass
def emit(self, payload: T):
pass
class A:
def __init__(self) -> None:
self.signal = Signal[A]()
def do(self) -> None:
self.signal.emit(self)
def handle_b(b: "B") -> None:
print(b.something)
class B(A):
def __init__(self) -> None:
super().__init__()
self.signal.connect(handle_b)
@property
def something(self) -> int:
return 42
I can provide the complete signal class as well but that just distracts from the problem. This leaves me with one error in mypy:
error: Argument 1 to "connect" of "Signal" has incompatible type Callable[[B], None]; expected Callable[[A], None]
Since the signal handling is implemented in A
the subclass B
can't expect B
type objects to be returned even though it clearly should be fine...
The connector
passed to Signal[A]
is of type Callable[[A], None]
, which means it has to promise to be able to handle any instance of A
(or any of it's sub-classes). handle_b
cannot fulfill this promise, since it only works for instances of B
, it therefore cannot be used as a connector
for a signal
of type Signal[A]
.
Presumably, the connector
of the signal
of any instance of B
will only ever be asked to handle an instance of B
, it therefore doesn't need to be of type Signal[A]
, but Signal[B]
would be sufficient. This means the type of signal
is not fixed, but varies for different sub-classes of A
, this means A
needs to be generic.
The answer by ogurets correctly makes A
generic, however there is no a problem with do
, since it's unclear whether self
is of type expected by self.signal.emit
. We can promise that these types will always match by annotating self
with the same type variable used for Signal
. By using a new type variable _A
which is bound by A
, we tell mypy that self
will always be a subtype of A
and therefore has a property signal
.
from __future__ import annotations
from collections.abc import Callable
from typing import TypeVar, Generic
T = TypeVar('T')
class Signal(Generic[T]):
def connect(self, connector: Callable[[T], None]) -> None:
pass
def emit(self, payload: T):
print(payload)
_A = TypeVar('_A', bound='A')
class A(Generic[_A]):
signal: Signal[_A]
def __init__(self) -> None:
self.signal = Signal[_A]()
def do(self) -> None:
self.signal.emit(self)
def handle_b(b: "B") -> None:
print(b.something)
class B(A['B']):
def __init__(self) -> None:
super().__init__()
self.signal.connect(handle_b)
@property
def something(self) -> int:
return 42
b = B()
reveal_type(b.signal) # Revealed type is '...Signal[...B*]'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With