How to have inherited type hints in python?

python typevar
python type alias
python type hints keyword argument
python type hints inheritance
python type comments
python function type
python generic types
python annotations

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 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.

Subclass in type hinting, When you do cls: A , you're saying that cls is going to an instance of type A . To make it work with type or its subtypes use typing.Type . This module provides runtime support for type hints as specified by PEP 484, PEP 526, PEP 544, PEP 586, PEP 589, and PEP 591. The most fundamental support consists of the types Any, Union, Tuple, Callable, TypeVar, and Generic. For full specification please see PEP 484. For a simplified introduction to type hints see PEP 483.

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

typing — Support for type hints — Python 3.8.5 documentation, Generics. Since type information about objects kept in containers cannot be statically inferred in a generic way, abstract base classes have been� The type hints syntax has been pretty stable for a while now and in my opinion you should be using it, or at least have it on your radar to implement it when you can.

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*]'

PEP 484 -- Type Hints, So many of the examples have a dual purpose: show how to write the annotation, and show the inferred types. Variables�. Python 3.6 introduced a syntax for� By using the super() function, you do not have to use the name of the parent element, it will automatically inherit the methods and properties from its parent.

Type hints cheat sheet (Python 3) — Mypy 0.782 documentation, If you want to just get a quick glimpse of how type hints work in Python, and see This is done by inheriting from Protocol and defining the function signatures� Type comments cannot be used with the class-based syntax, for consistency with the class-based NamedTuple syntax. (Note that it would not be sufficient to support type comments for backwards compatibility with Python 2.7, since the class definition may have a total keyword argument, as discussed below, and this isn't valid syntax in Python 2.7.)

Python Type Checking (Guide) – Real Python, type consuming (I'm writing/annotating a subclass and have to copy potentially My idea here is to introduce some kind of marker (perhaps a @typing. inherit_type_hints untyped defs on derived classes python/mypy#3903. The official home of the Python Programming Language. Non-goals. While the proposed typing module will contain some building blocks for runtime type checking -- in particular the get_type_hints() function -- third party packages would have to be developed to implement specific runtime type checking functionality, for example using decorators or metaclasses.

Proposal: inheritance + annotations � Issue #269 � python/typing , Type hints make it trivial to find where a given class is used when you're trying to refactor your code base. While many IDEs already have some� Update 26 March 2017 This appears to have been fixed as of Python 3.5.3 and 3.6.0, so there's no longer a need to additionally inherit from collections.abc.Iterable. Getting type hints programmatically. If you have a need to, typing.get_type_hints() can be used to programmatically inspect type hints:

Comments
  • Well, the error is correct, you did constrain self.signal to A only: self.signal = Signal[A]().
  • You basically set T = A there. B may be a subclass, but because A.something doesn't exist, you can't use that attribute. You tied everything down to A now. A method that accepts B is obviously allowed to use B.something so the base class A can't ever satisfy that requirement.
  • shouldn't there be a way to tell the type Hinting to accept subtypes of A? I mean disabling the typing works, but then why have type hinting at all...
  • Sorry, had to step away from the keyboard for a while; it probably is possible to use a TypeVar() marked as covariant instead of A but won’t be able to test until tomorrow.
  • I did already try that, I get a problem.py:10: error: Cannot use a covariant type variable as a parameter on the line: def emit(self, payload: T): And still have the old error :D maybe upgrade my mypy version...
  • nice idea, but I still have errors on the emit of class A and the emit function definition itself, with mypy 0.600
  • Hmm.. Last version of pip's mypy (0.5.0) looks fine. Sadly, too busy to dive deeper into this for now.