3

You can’t use typing types like Dict[str, int] in an isinstance check:

Python 3.7.6 (default, Dec 30 2019, 19:38:28)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.12.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from typing import Dict

In [2]: myvar = {"a": 1}

In [3]: isinstance(myvar, Dict[str, int])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-a8fee57141ae> in <module>
----> 1 isinstance(myvar, Dict[str, int])

However any library that does type-checking needs to be able to do something kind of like isinstance(myvar, Dict[str, int]) (...I realise it should be called something different than isinstance, that this is not the exactly same thing)

I feel like the equivalent function that works for typing must exist somewhere, maybe in mypy project? (but there’s a lot of complicated code in there and I couldn’t find it so far)

There are plenty of projects besides mypy which need this, libraries like pydantic for example, and AFAICT they all have complicated hand-rolled implementations and it seems like there are lots of edge cases (or just… ‘cases’) which have to be enumerated and covered. This leads to bugs/limited type recognition e.g. https://github.com/bloomberg/attrs-strict/issues/27

It seems like there is a need for a canonical implementation of this functionality. Does one exist already somewhere that I haven’t found?

I give you a motivating example from the Python stdlib:

https://docs.python.org/3/library/functools.html#functools.singledispatch

For functions annotated with types, the decorator will infer the type of the first argument automatically:

>>> @fun.register
... def _(arg: int, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: list, verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

Well that's pretty cool. But it's cheating, because these days generally we wouldn't annotate the second function using list builtin, but rather something like List[str]... and that doesn't work, because singledispatch is just doing a naive isinstance check, and that can't handle typing generics. So singledispatch doesn't really support dispatch by type-annotation like it claims to.

Anentropic
  • 32,188
  • 12
  • 99
  • 147
  • mypy is all about static checks. The whole `typing` system is really geared towards static checks - runtime introspection seems like an afterthought. Even things like `typing.get_args` that you'd expect to be in from day 1 took until Python 3.8 to be introduced. That was over 4 years after `typing` itself went in in Python 3.5. – user2357112 Apr 02 '20 at 09:55
  • Does this answer your question? [How do I check if a value matches a type in python?](https://stackoverflow.com/questions/55503673/how-do-i-check-if-a-value-matches-a-type-in-python) – Georgy Apr 02 '20 at 10:22
  • @Georgy kind of, yes, in that there is seemingly no good answer, just various people struggling against the lack of support for this kind of checking in the core `typing` library. Here is another related question with some good answers https://stackoverflow.com/questions/50563546/validating-detailed-types-in-python-dataclasses/50622643 – Anentropic Apr 02 '20 at 11:18

1 Answers1

6

No, there's no canonical check of this sort. As the commenter says, typing was introduced for static type checks, and i think many of the core developers think it should stay like that.

The nearest thing I can think of is pydantic's parse_obj_as. It's different in that it tries to coerce an object into a specific type, and raises an error if it fails, but it's quite close.

Usage:

from pydantic import parse_obj_as
from typing import Dict

parse_obj_as(Dict[str, int], {'xxx': 1, 'yyy': 2})
#> {'xxx': 1, 'yyy': 2}

parse_obj_as(Dict[str, int], {'xxx': 1, 123: '12'})
#> {'xxx': 1, '123': 12}

parse_obj_as(Dict[str, int], ['not a dict'])
#> ValidationError: 1 validation error for ParsingModel[Dict[str, int]]
#> __root__
#>   value is not a valid dict (type=type_error.dict)

Docs here.

Note: I build pydantic, so I'm a little biased.

SColvin
  • 11,584
  • 6
  • 57
  • 71
  • Thanks for answering, and thanks for Pydantic - that library is so great! (I seem to find a use for it in every project I do these days...) This makes it very clear Pydantic's approach, which I interpret as a pragmatic EAFP ("try to instantiate as") rather than strict validation. – Anentropic Apr 02 '20 at 11:13