Repository navigation
Use case for typing.Type with abstract types #4717
Description
Activity
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.Reacted by Vladislav A. Proskurov, andrechalella, Micha Sengotta and Thibaud Wilson StettlerAs far as I can tell, the same problem applies to
Protocolas well. Is there any way to have a TypeVar that references anything abstract?Reacted by Dan Ballance, Petr Motejlek, McSinyx, davebirch, Vic Chen, Bazyli Cyran, Maddy Guthridge, Ludwik Trammer, Draeli, Vladislav A. Proskurov and 2 more@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.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 ;-).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.
Reacted by andrechalella@JukkaL 🙏
@JukkaL I think I found a workaround, leveraging the little white lie that
Protocols without constructors are callables that return themselves:from typing import Callable, Type, TypeVar from typing_extensions import Protocol class AProtocol(Protocol): x: int protocol = TypeVar("protocol") def adherent(c: Callable[[], protocol]) -> Callable[[Type[protocol]], Type[protocol]]: def decor(input: Type[protocol]) -> Type[protocol]: return input return decor @adherent(AProtocol) # No error; Yes is the expected shape class Yes(object): x: int other: str y = Yes() y.x y.other @adherent(AProtocol) # We get an error here, as desired class No(object): y: int
Reacted by Rahix and Timothy MooreI 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
Adherent[AProtocol](Yes) Adherent[AProtocol](No)but these have to come after the class body and look somewhat uglier.
I'm not sure what's the best way to move forward, though.
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.
Reacted by GlyphThis "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).from abc import ABC from typing import TypeVar, Type T = TypeVar('T') class Abstract(ABC): pass def isthisaninstance(this, type_: Type[T]) -> bool: return isinstance(this, type_) isthisaninstance("", Abstract) # type: ignore :(
Is there any way to overcome this (other than to
# type: ignoreall function/method calls?Reacted by Michael Cousins, Miles Rufat-Latre, Andreas H., Christian Ciach, Peter Schutt, Daniil Kharkov, Evan Kuhn, Ludwik Trammer and Vladislav A. ProskurovReacted by Christian Ciach and Evan KuhnIs there any way to overcome this
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:from typing import TYPE_CHECKING if TYPE_CHECKING: Base = object else: Base = ABC class Abstract(Base): pass
You'll lose any ABC checking by mypy, however.
85 remaining items
- added a commit that references this issue
on Mar 11, 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.Reacted by Andrew Howe, Darsey Litzenberger, Maurice Müller, Stanislav Terliakov, Arkadiusz Pajor, uqzx, Thomas, Alexey Bondarenko, andrechalella, Cristian M and 1 moreReacted by Neil Girdhar, ippei, Rafal Krupinski, vincent-guyon_Atos, Maurice Müller, José María Fernández, Arkadiusz Pajor, uqzx, Thomas, andrechalella and 1 more- added 2 commits that reference this issue
on Sep 24, 2025 def get_concrete[T: type[ABC]](interface: T) -> T: ...does not work in my case. I still getOnly concrete class can be given where
Any 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
- added 2 commits that reference this issue
on Oct 16, 2025 - added 3 commits that reference this issue
on Oct 20, 2025 - added a commit that references this issue
on Oct 21, 2025 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
Interesting.
If anybody have a work around, it would be very convenient.
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:
from typing_extensions import TypeVar, Mapping, Protocol, Sequence, runtime_checkable @runtime_checkable class ModelProtocol(Protocol): """A protocol that all models should implement.""" @property def name(self) -> str: ... @runtime_checkable class SubModelProtocol(ModelProtocol, Protocol): """A more specific protocol that some models may implement.""" def specific_method(self) -> None: ... P = TypeVar("P", bound=ModelProtocol) def filter_models( models: Mapping[str, ModelProtocol], proto: type[P], choices: Sequence[str] | None = None, ) -> list[P]: """Filter a map of models by a specific protocol type. Parameters ---------- models : ``Mapping[str, ModelProtocol]`` Mapping of model names to model instances. proto : ``type[Any]`` The protocol type to filter for. choices : ``Sequence[str]``, optional If provided, return only models associated with names in this sequence. Default is ``None`` (all ``proto`` models are returned). Returns ------- list[P] List of model instances that implement the given protocol. """ if choices is not None: return [ model for name, model in models.items() if isinstance(model, proto) and name in choices ] return [model for model in models.values() if isinstance(model, proto)] class ExampleModel: """An example model implementing ModelProtocol.""" def __init__(self, name: str): self._name = name @property def name(self) -> str: return self._name class ExampleSubModel: """An example sub-model implementing SubModelProtocol.""" def __init__(self, name: str): self._name = name @property def name(self) -> str: return self._name def specific_method(self) -> None: print(f"Specific method called on {self._name}") objs: Mapping[str, ModelProtocol] = { "model1": ExampleModel("Model 1"), "submodel1": ExampleSubModel("SubModel 1"), } filtered = filter_models(objs, SubModelProtocol) assert len(filtered) == 1
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.
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 ofsome_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):
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:
Unfortunately, MyPy complains about this as follows:
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:
isinstance