Python 3.7: проверьте, является ли аннотация типа "подклассом" универсального - PullRequest
0 голосов
/ 19 декабря 2018

Я пытаюсь найти надежный / кросс-версия (3.5+) способ проверки того, является ли аннотация типа «подклассом» данного универсального типа (т.е. получить универсальный тип из объекта аннотации типа).

В Python 3.5 / 3.6 он работает на одном дыхании, как и следовало ожидать:

>>> from typing import List

>>> isinstance(List[str], type)
True

>>> issubclass(List[str], List)
True

В то время как на 3.7 похоже, что экземпляры универсальных типов больше не являются экземплярами type, поэтому он потерпит неудачу:

>>> from typing import List

>>> isinstance(List[str], type)
False

>>> issubclass(List[str], List)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.7/typing.py", line 716, in __subclasscheck__
    raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks

Другие идеи, которые приходят на ум, проверяют фактический тип экземпляра, но:

Python 3.6 / 3.5:

>>> type(List[str])
<class 'typing.GenericMeta'>

Python 3.7:

>>> type(List[str])
<class 'typing._GenericAlias'>

Но это на самом деле не дает никаких дополнительных указаний относительно того, какой фактический универсальный тип является действительным (возможно, это не List);кроме того, кажется, что делать проверку таким образом неправильно, тем более что _GenericAlias теперь стал «закрытым» типом (обратите внимание на подчеркивание).

Еще одна вещь, которую можно проверить, это аргумент __origin__на тип, но это не похоже на правильный способ сделать это либо.

И он по-прежнему отличается на 3,7:

>>> List[str].__origin__
<class 'list'>

в то время как 3,5 / 3,6:

>>> List[str].__origin__
typing.List

Я искал "правильный" способ сделать это, но не нашел его в поиске документов / Google Python.

Теперь я предполагаю, что должен бытьчистый способ сделать эту проверку, так как такие инструменты, как mypy, будут полагаться на него при выполнении проверок типов ..?

Обновление: об сценарии использования

Хорошо, добавьте немного больше контекста здесь ..

Итак, мой вариант использования для этого - использовать интроспекцию для сигнатур функций (типы аргументов / значения по умолчанию, тип возвращаемого значения, строка документации), чтобы автоматически генерировать для них схему GraphQL (таким образом уменьшая количество шаблонов).

Я все еще немного волнуюсь, будет ли этохорошая идея или нет.

Мне она нравится с точки зрения удобства использования (нет необходимости изучать еще один способ объявления сигнатуры вашей функции: просто аннотируйте ваши типы обычным способом);посмотрите два примера кода, чтобы понять, что я имею в виду: https://github.com/rshk/pyql

Интересно, добавляет ли поддержка универсальных типов (списков, диктов, объединений, ...) с использованием типов из typing слишком много "черная магия », которая может сломаться неожиданным образом.(На данный момент это не большая проблема, но как насчет будущих версий Python после 3.7? Это станет кошмаром обслуживания?).

Конечно, альтернативой может быть просто использование пользовательской аннотации типа, котораяподдерживает более надежную проверку на будущее, например: https://github.com/rshk/pyql/blob/master/pyql/schema/types/core.py#L337-L339

.. но с другой стороны, это заставит людей помнить, что они должны использовать аннотацию пользовательского типа.Более того, я не уверен, как mypy справится с этим (я предполагаю, что где-то должно быть объявление, чтобы сказать, что пользовательский тип полностью совместим с typing.List ..? По-прежнему звучит хакерски).

(Я в основном спрашиваю предложения по двум подходам, и что наиболее важно, любые плюсы / минусы двух альтернатив, которые я мог бы пропустить. Надеюсь, это не станет «слишком широким» для SO ..).

1 Ответ

0 голосов
/ 17 января 2019

Прежде всего: не существует API, определенного для интроспективных объектов хинтинга, как определено модулем typing.Ожидается, что инструменты подсказки типов будут иметь дело с исходным кодом , то есть с текстом, а не с объектами Python во время выполнения;mypy не анализирует List[str] объектов, вместо этого он обрабатывает абстрактное синтаксическое дерево вашего исходного кода.

Итак, вы всегда можете получить доступ к таким атрибутам, как __origin__,По сути, вы имеете дело с деталями реализации ( внутренняя бухгалтерия ), и эти детали реализации могут и будут меняться от версии к версии.

Тем не менее, основной участник mypy / typing создал модуль typing_inspect для разработки API самоанализа для подсказок типов.Проект по-прежнему документирует себя как экспериментальный , и вы можете ожидать, что со временем он тоже изменится, пока он больше не будет экспериментальным.Это не решит вашу проблему здесь, так как он не поддерживает Python 3.5, и его функция get_origin() возвращает те же самые значения, которые предоставляет атрибут __origin__.

Со всеми этими предостережениями вне путик Python 3.5 / Python 3.6 вы хотите получить доступ к атрибуту __extra__;это базовый встроенный тип, используемый для поддержки поддержки issubclass() / isinstance(), которая изначально была реализована в библиотеке (но удалена в 3.7):

def get_type_class(typ):
    try:
        # Python 3.5 / 3.6
        return typ.__extra__
    except AttributeError:
        # Python 3.7
        return typ.__origin__

Это создает <class 'list'> в Python 3.5и выше, независимо.Он по-прежнему использует внутренние детали реализации и вполне может сломаться в будущих версиях Python.

...