Repository navigation
Use case for typing.Type with abstract types #4717
Description
In #2169 and #1843 there was discussion about using Type[some_abc] and how it should be allowed in a function signature, but that the call-site was expected to pass a concrete subclass of some_abc. There is an implicit assumption that the objective of a function taking such a type is, ultimately, to instantiate the type.
@gvanrossum said, in #2169 (comment):
But maybe we need a check that you call this thing with a concrete subclass;
and for that we would need some additional notation (unless maybe we could
just say that whenever there's an argument of Type[A] where A is abstract,
that the argument must be a concrete subclass. But for that we'd need some
experiment to see if there's much real-world code that passes abstract
classes around. (If there is, we'd need to have a way to indicate the need
in the signature.)
I have such a use case.
I have a sequence of observers supplied by clients of my library, to which I want to dispatch events according to the abstract base class(es) that each implements. I tried to do this as follows:
import abc
import typing
class FooObserver(metaclass=abc.ABCMeta):
"""Receives Foo events."""
@abc.abstractmethod
def on_foo(self, count: int) -> None:
raise NotImplementedError()
class BarObserver(metaclass=abc.ABCMeta):
"""Receives Bar events."""
@abc.abstractmethod
def on_bar(self, status: str) -> None:
raise NotImplementedError()
class Engine:
def __init__(self, observers: typing.Sequence[typing.Any]) -> None:
self.__all_observers = observers
def do_bar(self, succeed: bool) -> None:
status = 'ok' if succeed else 'problem'
for bar_observer in self.__observers(BarObserver):
bar_observer.on_bar(status)
def do_foo(self, elements: typing.Sequence[typing.Any]) -> None:
count = len(elements)
for foo_observer in self.__observers(FooObserver):
foo_observer.on_foo(count)
__OBSERVER_TYPE = typing.TypeVar('__OBSERVER_TYPE')
def __observers(
self,
observer_type: typing.Type['__OBSERVER_TYPE']
) -> typing.Sequence['__OBSERVER_TYPE']:
return [observer for observer in self.__all_observers
if isinstance(observer, observer_type)]Unfortunately, MyPy complains about this as follows:
/Users/erikwright/abc_typing.py:24: error: Only concrete class can be given where "Type[BarObserver]" is expected
/Users/erikwright/abc_typing.py:29: error: Only concrete class can be given where "Type[FooObserver]" is expected
Given that (AFAICT) the decision was made to not attempt to verify that the runtime type supports any specific constructor signature I'm wondering why there is nonetheless an expectation that the runtime type is constructable at all. In my case, the entire purpose of typing here is:
- Require you to actually pass a type, which means I can use it in
isinstance - Allow me to specify the return type of the method in terms of the supplied type.
Activity
ilevkivskyi commentedon Mar 17, 2018
The point is that it is too hard to track which classes are instantiated in the body of a given function, and which are not. If we would have such tracking, then we could allow calling functions with abstract classes at particular positions.
Taking into account that such tracking would take some effort, and that during the year of current behaviour, this is a first such request, I would recommend just using
# type: ignore. Even if this will be implemented at some point, this is quite low priority.glyph commentedon Jan 15, 2019
As far as I can tell, the same problem applies to
Protocolas well. Is there any way to have a TypeVar that references anything abstract?JukkaL commentedon Jan 15, 2019
@glyph Perhaps you could use
# type: ignoreto silence the error as suggested above? It's clearly not optimal, though. Can you give more information about your use case?I wonder if we could enable
Type[x]with abstract and protocol types but disallow creating an instance (i.e they wouldn't be callable). They could still be used for things likeisinstancechecks.glyph commentedon Jan 15, 2019
What I’m trying to do is to write a class decorator,
@should_implement(SomeProtocol)which type-checks the decorated class to ensure it complies with the given protocol, so ignoring the error would obviate the whole point ;-).JukkaL commentedon Jan 15, 2019
Makes sense, though I'm not sure if lifting the restriction would be sufficient to allow the decorator to be checked statically. Right now a runtime check is probably the best you can do with that syntax, at least without a plugin. For a static check you could use a dummy assignment (which is not very pretty).
Increasing priority to normal since I think that the current approach is too restrictive. I'm not sure what's the best way to move forward, though.
glyph commentedon Jan 15, 2019
@JukkaL 🙏
glyph commentedon Jan 16, 2019
@JukkaL I think I found a workaround, leveraging the little white lie that
Protocols without constructors are callables that return themselves:glyph commentedon Aug 6, 2019
I should note that there's a big problem with my workaround; you can only apply the decorator once, and then it breaks down. There's a variant here where you can abuse a Generic instead, and then write
but these have to come after the class body and look somewhat uglier.
ilevkivskyi commentedon Aug 7, 2019
A possible ad-hoc solution (which may be not so bad), is to just remove this check for class decorators, because it also causes troubles for dataclasses, see #5374 that has 12 upvotes.
petr-motejlek commentedon Feb 1, 2020
This "Only concrete class can be given" error also seems impossible to overcome for code that is supposed to accept an abstract class and return some instance of that class, even if it would have to create the class right then and there using some fancy mechanism like
type(a, b, c). Thinkunittest.Mockand similar dummy object factories.I have also just realized that this breaks (mypy raises this error) even when you write a function that acts like
isinstance(...)with the provided class. Makes you wonder howisinstanceis actually typed in typeshed (gonna check that now).Is there any way to overcome this (other than to
# type: ignoreall function/method calls?JukkaL commentedon Feb 4, 2020
In some cases you may use
if TYPE_CHECKING:to conditionally define the base class so that mypy sees the base class asobjectwhile at runtime it will beABC:You'll lose any ABC checking by mypy, however.
85 remaining items
Load band_mask models for gpucbf.antenna-channelised-voltage streams
rilshok commentedon May 19, 2025
type-abstractshould be disabled.The annotation
type[T]describes a class object, not whether it can be instantiated. Rejecting abstract classes here conflates type identity with instantiability, which are orthogonal concerns.Many valid use cases, such as dependency injection, plugin registration, and factory patterns, require passing abstract types without instantiating them. This check blocks these patterns unnecessarily and forces developers to use
# type: ignore, which defeats the purpose of static typing.If instantiability needs to be enforced, it should be done explicitly (e.g., via
Callable[..., T]), not assumed bytype[T].Allowing abstract classes in
type[T]improves flexibility, reduces false positives, and aligns better with real-world Python usage.Type ignore known mypy limitations
Type ignore known mypy limitations
dpinol commentedon Oct 6, 2025
def get_concrete[T: type[ABC]](interface: T) -> T: ...does not work in my case. I still getOnly concrete class can be given whereAny other workaround?
What is weird is that def cast(typ: type[_T], val: Any) -> _T function itself does not have this restriction about the classes not being abstract
Type ignore known mypy limitations
Type ignore known mypy limitations
Type ignore known mypy limitations
Type ignore known mypy limitations
Add abc all-around (#4096)
Add abc all-around (teemtee#4096)
vallsv commentedon Nov 7, 2025
Interesting.
If anybody have a work around, it would be very convenient.
jacopoabramo commentedon Dec 10, 2025
Adding my use case to the choir: I've been struggling for a bit with a relatively small function that filters an object based on an input protocol. A minimal example:
The key problem is that
isinstancerefuses to acknowledgeprotoas correct input type, even though by having a look around it is what some runtime type checkers also use for their checks.I opened a discussion in the Python discourse where it was told me that PEP 747 (
TypeForm) might be a solution, but for the life of me I can't figure out how to use it and it seems it is not supposed to be used withisinstance.