Python 3.7: проверьте, является ли аннотация типа подклассом общего

Я пытаюсь найти надежный/кросс-версию (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

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

Питон 3.6/3.5:

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

Питон 3.7:

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

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

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

А на 3.7 все равно отличается:

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

а 3,5/3,6:

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

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

Теперь я предполагаю, что должен быть чистый способ выполнить эту проверку, поскольку такие инструменты, как 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..).


person redShadow    schedule 19.12.2018    source источник
comment
Почему вы пытаетесь сделать эту проверку? Каков ваш вариант использования?   -  person Martijn Pieters    schedule 17.01.2019
comment
Самая большая проблема, которую я здесь вижу, заключается в том, что не определено API для обработки typing внутренних компонентов. Есть только синтаксис. Статические средства проверки типов обрабатывают текст, а не объекты, поэтому им не нужно обрабатывать List[str] как объект. В лучшем случае инструмент создаст AST из токенизированного ввода. __origin__ — это неопубликованная деталь реализации (в комментариях в typing.py они называются внутренняя бухгалтерия), поэтому полагаться на них в своих проектах — на свой страх и риск.   -  person Martijn Pieters    schedule 17.01.2019
comment
Кажется, нет хорошего или официального способа сделать это, но вам может быть интересен typing_inspect< /a> библиотека и как она к этому подходит.   -  person jonafato    schedule 17.01.2019
comment
@jonafato: я собирался упомянуть typing_inspect, но эта библиотека тоже даст вам <class 'list'> на Python 3.7 и typing.List на Python 3.6. И он еще не поддерживает Python 3.5.   -  person Martijn Pieters    schedule 17.01.2019
comment
@jonafato: то, что typing_inspect имеет в виду, так это то, что он разрабатывается основным участником mypy и после стабилизации, вероятно, станет частью основной библиотеки. Но я не думаю, что то, чего хочет ОП, может быть достигнуто на данный момент. Подсказка типа слишком сильно меняется между 3,5–3,7.   -  person Martijn Pieters    schedule 17.01.2019


Ответы (3)


Прежде всего: не существует 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.

person Martijn Pieters    schedule 17.01.2019
comment
Спасибо, Martijin, следите за typing_inspect, похоже, это будет правильный путь, как только он будет завершен / стабилен. Я обновил свой вопрос, указав больше контекста о моем варианте использования, кстати. - person redShadow; 25.01.2019
comment
@redShadow: правильно, вы хотите перегрузить подсказку типа с помощью вашего собственного сопоставления системы типов. Я не уверен, насколько хорошо это будет сидеть с течением времени. Я знаю, что @functools.singledispatch() поддержка подсказок типа намеренно избегает поддержки дженериков. . - person Martijn Pieters; 25.01.2019
comment
@redShadow: самая большая проблема здесь заключается в том, что аннотации типов и то, как мы их используем, все еще меняются, поскольку разработчики выясняют, как лучше всего представлять типы, используемые в реальных проектах, и как это сделать. все работают на питоне. Модуль typing претерпел так много обновлений в Python 3.7, потому что базовый язык получил некоторые крючки специально для избегайте взломов, необходимых для работы typing. Таким образом, вы увидите еще несколько изменений, когда попытаетесь использовать это так, как оно изначально не предназначалось. - person Martijn Pieters; 25.01.2019
comment
Понял. Каковы шансы, что когда-нибудь поддержка интроспекции во время выполнения полностью исчезнет? Это моя самая большая забота сейчас. Глядя на typing_inspect и @singledispatch (я предполагаю, что все хотели бы, чтобы когда-нибудь он также поддерживал дженерики..?), риск того, что это произойдет, кажется близким к нулю, но..? - person redShadow; 25.01.2019
comment
Я действительно не могу сказать. Это надо напрямую у разработчиков спрашивать. Возможно, вы могли бы попробовать опубликовать сообщение в списке рассылки Python-Dev. Тем не менее, я очень сомневаюсь, что интроспекция во время выполнения когда-либо исчезнет, ​​это пойдет вразрез с преобладающей культурой Python. - person Martijn Pieters; 25.01.2019

Python 3.8 добавляет typing.get_origin() и typing.get_args() для поддержки базового самоанализа.

Эти API также были перенесены на Python ›=3.5 в https://pypi.org/project/typing-compat/.

Имейте в виду, что поведение typing.get_args все еще немного отличается в 3.7 при вызове на голых дженериках; в 3.8 typing.get_args(typing.Dict) это (), но в 3.7 это (~KT, ~VT) (и аналогично для других дженериков), где ~KT и ~VT — объекты типа typing.TypeVar.

person Max Gasner    schedule 14.10.2019

pip install typing_utils

затем

>>> typing_utils.issubtype(typing.List[int], list)
True

>>> typing_utils.issubtype(typing.List, typing.List[int])
False

typing_utils также поддерживает typing.get_origin и typing.get_args с Python 3.8 на 3.6+.

person hrmthw    schedule 21.11.2020
comment
typing_utils (pypi.org/project/typing_utils) выпущен всего два часа назад и явно находится в ранней версии 0.0.1 и к тому же не имеет Development Status в setup.py. Вы уверены, что он достаточно стабилен и готов к работе? и какие ключевые преимущества перед пакетом typing v3.7.4.3? - person cizario; 21.11.2020