Reputation: 77137
I'm having trouble expressing the precise type hints for a pair of parameters where one needs to be a value that is an instance of some type, and the other needs to be the type itself, or some super-type.
One case where you see this in Python is in the __exit__
method for a context manager (class).
import typing as t
from types import TracebackType
from contextlib import AbstractContextManager
class Acme(AbstractContextManager):
def __exit__(self, exc_type: t.Type[Exception], exc: Exception,
tb: Tracebacktype) -> None:
...
This specific case may not matter, because context managers are handled internally by Python, but I'd still like to understand how to express two arguments where the first one is the (super-)type of the second one.
Conceptually, my problem is I want to express that the value exc
has the type exc_type
or some subtype. In the expression above, I guess mypy would be perfectly happy with arguments like LookupError, RuntimeError('foo')
even though a RuntimeError
isn't a type of LookupError
. Is there a better way to express this where mypy would catch that kind of mistake?
Update
Trying a test case here using TypeVars:
import typing as t
C = t.TypeVar('C', bound=t.Collection)
def f(t: t.Type[C], c: C) -> None:
pass
f(t.Dict, [])
I'd expect mypy to complain about this code, because even though the empty list is a type of Collection, it's not a dictionary.
Upvotes: 4
Views: 1282
Reputation: 7210
This kind of use of Type[some_type_var]
is seemingly discouraged by a seciton in the PEP where only a subset of this support is provided (notably for binding TypeVar
s to class types). It appears this discouragement is not phillisophical (this would be a common use of generics in a language where types and values are both first-class) - but more related to the practical implementation of type checkers. The use of TypeVar
for classmethod signatures - a special case of your question - was even a late addition to the pep.
Possible solutions that do not work with mypy (as of now):
TypeVar
There is no type checking in function signatures of Type[some_type_var]
. This is not variable based on if some_type_var
is contravariant
or covariant
(nor should it be).
TypeVar
of Type[TypeVar]
T = TypeVar("T")
Meta = TypeVar("Meta", bound=Type[T])
def foo(the_type: Meta, the_value: T):
....
T
and Meta
can not "bind" together .
To my surprise, in some cases you can squeeze out a bit of this behavior (but this is a special case and this seems undefined to me)
T = TypeVar('T', str, int, float, bytes)
class Foo:
def bar(self, the_type: Type[T], the_value: T):
print(isinstance(the_value, the_type))
f = Foo()
f.bar(str, "baz")
f.bar(str, b"baz") # wrong!
f.bar(int, 1)
f.bar(int, 3.15159) # wrong! (but no complaint)
f.bar(float, 1.0)
f.bar(float, 1) # wrong! (but no complaint)
f.bar(float, b'1.0') # wrong!
Giving
so.py:nn: error: Value of type variable "T" of "bar" of "Foo" cannot be "object"
so.py:nn: error: Value of type variable "T" of "bar" of "Foo" cannot be "object"
Found 2 errors in 1 file (checked 1 source file)
But only seemingly for python's primitive types (this will not work with user-space types) (and again, only some of python's primitive types as shown (see the error-miss with float
and int
). I think this is a clear "nice-to-have" extension of TypeVar
(and makes more Generic
use-cases possible).
There are some related issues on mypy about this:
Upvotes: 3
Reputation: 71517
This is what TypeVar
s are for! You want something like:
from types import TracebackType
from typing import Type, TypeVar
from contextlib import AbstractContextManager
_E = TypeVar('_E', bound=Exception)
class Acme(AbstractContextManager):
def __exit__(
self,
exc_type: Type[_E],
exc: _E,
tb: Tracebacktype
) -> None:
...
Upvotes: 0