Reputation: 181
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...
Upvotes: 16
Views: 17796
Reputation: 2302
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*]'
Upvotes: 1
Reputation: 628
from __future__ import annotations
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(Generic[T]):
def __init__(self) -> None:
self.signal = Signal[T]()
def do(self: A) -> None:
self.signal.emit(self)
def handle_b(b: B) -> None:
print(b.something)
class C:
pass
def handle_c(c: C) -> None:
print(c)
class B(A[B]):
def __init__(self) -> None:
super().__init__()
self.signal.connect(handle_b) # OK
self.signal.connect(handle_c) # incompatible type
@property
def something(self) -> int:
return 42
Upvotes: 0
Reputation: 1121168
The type hint error is entirely correct. You created a Signal
instance with A
as the type, in the __init__
method of A
:
self.signal = Signal[A]()
Passing in a subclass is fine, but all code interacting with that Signal
instance now has to work for A
instances only. handle_b()
on the other handrequires an instance of B
, and can't lower the requirement to A
instead.
Drop the constraint:
self.signal = Signal()
or create an instance in each subclass with the correct type.
Upvotes: 0