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.