Как получить доступ к аргументам типа typing.Generic?
typing
модуль обеспечивает базовый класс для общих подсказок типа: typing.Generic
класса.
Подклассы аргументов типа Generic
accept в квадратных скобках, например:
list_of_ints = typing.List[int]
str_to_bool_dict = typing.Dict[str, bool]
Мой вопрос: как я могу получить доступ к этим аргументам типа?
То есть, учитывая str_to_bool_dict
качестве входных данных, как я могу получить str
и bool
качестве вывода?
В основном я ищу такую функцию, что
>>> magic_function(str_to_bool_dict)
(<class 'str'>, <class 'bool'>)
Ответы
Ответ 1
Возможность 1
Начиная с Python 3.6. есть общедоступное __args__
и (__parameters__
). Например:
print( typing.List[int].__args__ )
Он содержит общие параметры (т. __parameters__
int
), а __parameters__
содержит сам общий __parameters__
(т. __parameters__
~T
).
Возможность 2
Использовать typing_inspect.getargs
Что использовать
typing
следует за PEP8. Как PEP8, так и typing
являются соавторами Guido van Rossum. Двойное ведущее и заключительное подчеркивание определяется как: "волшебные" объекты или атрибуты, которые живут в управляемых пользователем пространствах имен ".
Глубины также прокомментированы в строке; из официального репозитория для ввода мы можем видеть: * " __args__
- кортеж всех аргументов, используемых в __args__
, например, Dict[T, int].__args__ == (T, int)
".
Тем не менее, авторы также отмечают: * "Типичный модуль имеет временный статус, поэтому он не покрывается высокими стандартами обратной совместимости (хотя мы стараемся как можно больше сохранить его), это особенно верно для (еще недокументированных) такие как __union_params__
. Если вы хотите работать с типизированными типами в контексте выполнения, тогда вам может быть интересен проект typing_inspect
(часть которого может закончиться впечатыванием позже). "
Я, генерал, что бы вы ни делали с typing
, должен быть обновлен до настоящего времени. Если вам нужны передовые совместимые изменения, я бы рекомендовал писать собственные классы аннотаций.
Ответ 2
Кажется, что этот внутренний метод сделает трюк
typing.List[int]._subs_tree()
который возвращает кортеж:
(typing.List, <class 'int'>)
Но это частный API, возможно, есть лучший ответ.
Ответ 3
Насколько мне известно, счастливого ответа здесь нет.
Что приходит в голову - это __args__
недокументированный, который хранит эту информацию:
list_of_ints.__args__
>>>(<class 'int'>,)
str_to_bool_dict.__args__
>>>(<class 'str'>, <class 'bool'>)
но об этом не упоминается в документации модуля typing
.
Стоит отметить, что в документации было очень близко указано:
Вероятно, нам также следует обсудить, нужно ли документировать все аргументы ключевого слова для GenericMeta.__new__
. Существуют tvars
, args
, origin
, extra
и orig_bases
. Я думаю, мы могли бы сказать что-то о первых трех (они соответствуют __parameters__
, __args__
и __origin__
и они используются большинством вещей при наборе текста).
Но это не совсем так:
Я добавил GenericMeta
в __all__
и добавил docstrings в GenericMeta
и GenericMeta.__new__
после обсуждения в проблеме. Я решил не описывать __origin__
и друзей в докстерах. Вместо этого я просто добавил комментарий в том месте, где они впервые используются.
Оттуда у вас все еще есть три взаимоисключающих варианта:
-
дождитесь, пока модуль typing
достигнет полной зрелости, и надеемся, что эти функции будут задокументированы в ближайшее время
-
присоединиться к списку рассылки идей Python и посмотреть, может ли быть собрана достаточная поддержка, чтобы сделать эти внутренние публикации общедоступными/частью API
-
тем временем работайте с недокументированными внутренностями, делая азартные игры, что не будет изменений к тем или иным изменениям.
Обратите внимание, что третья точка вряд ли можно избежать, так как даже API может быть изменен:
Типовой модуль был включен в стандартную библиотеку на временной основе. Новые функции могут быть добавлены, и API может измениться даже между незначительными версиями, если это будет сочтено необходимым для основных разработчиков.
Ответ 4
Используйте .__args__
в ваших конструкциях. Итак, вам нужна волшебная функция -
get_type_args = lambda genrc_type: getattr(genrc_type, '__args__')
Мой вопрос: как я могу получить доступ к этим аргументам типа?
В таких ситуациях - как мне получить доступ...
Используйте мощные возможности интроспекции Python.
Даже в качестве программиста, не являющегося программистом, я знаю, что пытаюсь проверить материал, а dir
- это функция, похожая на IDE в терминале. Так после
>>> import typing
>>> str_to_bool_dict = typing.Dict[str, bool]
Я хочу посмотреть, есть ли что-нибудь, что делает волшебство, которое вы хотите,
>>> methods = dir(str_to_bool_dict)
>>> methods
['__abstractmethods__', '__args__', .....]
Я вижу слишком много информации, чтобы проверить, правильно ли я проверяю
>>> len(methods)
53
>>> len(dir(dict))
39
Теперь давайте найдем методы, которые были разработаны специально для общих типов
>>> set(methods).difference(set(dir(dict)))
{'__slots__', '__parameters__', '_abc_negative_cache_version', '__extra__',
'_abc_cache', '__args__', '_abc_negative_cache', '__origin__',
'__abstractmethods__', '__module__', '__next_in_mro__', '_abc_registry',
'__dict__', '__weakref__'}
среди них __parameters__
, __extra__
, __args__
и __origin__
полезны. __extra__
и __origin__
не будут работать без себя, поэтому мы остаемся с __parameters__
и __args__
.
>>> str_to_bool_dict.__args__
(<class 'str'>, <class 'bool'>)
Отсюда и ответ.
Introspection позволяет py.test
assert
выводить устаревшие структуры тестирования JUnit. Даже такие языки, как JavaScript/Elm/Clojure, не имеют прямолинейной вещи, как dir
Python. Соглашение об именах Python позволяет вам открывать язык без фактического чтения (в некоторых случаях, таких как эти), документации.
Итак, охота с помощью самоанализа и чтения документации/списков рассылки, чтобы подтвердить ваши выводы.
PS К OP - этот метод также отвечает на ваш вопрос. Каков правильный способ проверить, является ли объект типизацией. Generic? если вы не можете зафиксировать список рассылки или быть занятым разработчиком, - это способ сделать это в python.
Ответ 5
Вопрос задается конкретно о typing.Generic
, но оказывается, что (по крайней мере, в более ранних версиях модуля typing
) не все подписываемые типы являются подклассами Generic
. В более новых версиях все подписываемые типы хранят свои аргументы в __args__
:
>>> List[int].__args__
(<class 'int'>,)
>>> Tuple[int, str].__args__
(<class 'int'>, <class 'str'>)
Однако в Python 3.5 некоторые классы, такие как typing.Tuple
, typing.Union
и typing.Callable
хранят их в различных атрибутах, таких как __tuple_params__
, __union_params__
или, как правило, в __parameters__
. Для полноты здесь приведена функция, которая может извлекать аргументы типа из любого подписываемого типа в любой версии Python:
import typing
if hasattr(typing, '_GenericAlias'):
# python 3.7
def _get_base_generic(cls):
# subclasses of Generic will have their _name set to None, but
# their __origin__ will point to the base generic
if cls._name is None:
return cls.__origin__
else:
return getattr(typing, cls._name)
else:
# python <3.7
def _get_base_generic(cls):
try:
return cls.__origin__
except AttributeError:
pass
name = type(cls).__name__
if not name.endswith('Meta'):
raise NotImplementedError("Cannot determine base of {}".format(cls))
name = name[:-4]
try:
return getattr(typing, name)
except AttributeError:
raise NotImplementedError("Cannot determine base of {}".format(cls))
if hasattr(typing.List, '__args__'):
# python 3.6+
def _get_subtypes(cls):
subtypes = cls.__args__
if _get_base_generic(cls) is typing.Callable:
if len(subtypes) != 2 or subtypes[0] is not ...:
subtypes = (subtypes[:-1], subtypes[-1])
return subtypes
else:
# python 3.5
def _get_subtypes(cls):
if isinstance(cls, typing.CallableMeta):
if cls.__args__ is None:
return ()
return cls.__args__, cls.__result__
for name in ['__parameters__', '__union_params__', '__tuple_params__']:
try:
subtypes = getattr(cls, name)
break
except AttributeError:
pass
else:
raise NotImplementedError("Cannot extract subtypes from {}".format(cls))
subtypes = [typ for typ in subtypes if not isinstance(typ, typing.TypeVar)]
return subtypes
def get_subtypes(cls):
"""
Given a qualified generic (like List[int] or Tuple[str, bool]) as input, return
a tuple of all the classes listed inside the square brackets.
"""
return _get_subtypes(cls)
Демонстрация:
>>> get_subtypes(List[int])
(<class 'int'>,)
>>> get_subtypes(Tuple[str, bool])
(<class 'str'>, <class 'bool'>)