Я пишу свои собственные классы для int, str, bool и т. д., на которых есть генераторы. Я использую это для фаззинга функции на основе аннотаций типа. Все идет нормально, за исключением нотации | для типа union. Если я наберу что-то вроде:
def test_handles_none_with_arged_types(
x: Int[0, 10] | List[Int] | Str | Dict[NoneType, List[NoneType]]
):
assert type(x) in [int, list, str, dict, list]
if type(x) == int:
assert x >= 0 and x <= 10
if type(x) == list:
assert all([el is None for el in x])
if type(x) == dict:
for k, v in x.items():
assert k is None
assert type(v) == list
for el in v:
assert el is None
Python дает мне следующую ошибку:
TypeError: unsupported operand type(s) for |: 'Int' and 'List'
Кажется, это потому, что Int[0,10] имеет тип своего класса pybt.typing.Int, а не type. Однако использование typing.Union работает просто отлично.
def test_handles_none_with_arged_types(
x: Union[Int[0, 10], List[Int], Str, Dict[NoneType, List[NoneType]]]
):
...
Есть ли способ обойти это? К сожалению, я не могу придумать способ отложить создание экземпляров Int или других типов, которые проиндексированы в их __class_getitem__ под.
Обновлено:
Вот полный класс (для списка):
class List:
def __init__(self, sub_type=_DEFAULT_SUB_TYPE, max_len=_DEFAULT_MAX_LEN):
self.max_len: int = _DEFAULT_MAX_LEN
self.sub_type = sub_type
if max_len is not None:
self.max_len = max_len
def __str__(self):
return "pybt.types.List"
def __class_getitem__(cls, parameters):
sub_type = None
max_len = None
if type(parameters) != tuple:
parameters = (parameters,)
if len(parameters) > 2:
raise TypeError("Expected 2 arguments: List[sub_type, max_length]")
if len(parameters):
sub_type = parameters[0]
if len(parameters) > 1:
max_len = parameters[1]
if max_len and max_len <= 0:
raise TypeError(f"Max Length of {cls.max_len} is less than or equal to 0")
return cls(sub_type, max_len)
Это Python 3.11.2. Я не уверен, что понял ваш второй вопрос. Не могли бы вы уточнить? Спасибо!
В частности, является ли Int подклассом typing.Generic или каким-либо другим типом с подпиской? Я не совсем уверен, как именно это работает во время выполнения с оператором |, но обычно это требование для проверки типов, поэтому я предполагаю, что это может быть уместно здесь.
Ах, нет, это не подкласс typing.Generic. Это свой собственный класс.
См. мое редактирование для определения класса...
это действительно странно, почему __class_getitem__ создает экземпляр класса!?
"с генераторами на них". что вы подразумеваете под "генераторами"? «Я использую это для фаззинга функции на основе аннотаций типа». «Что это значит?»
assert type(x) in [int, list, str, dict, list] не пройдет за ваши типы....
«Похоже, это потому, что Int[0,10] имеет тип своего класса pybt.typing.Int, а не тип. Однако использование typing.Union работает просто». Конечно, потому что Union не требует для работы какого-либо специального метода. . Вы можете предоставить все, что хотите






Вы уже знаете о методах dunder и type, поэтому извините, если этот ответ повторяет для вас очевидное.
Чтобы Int() | List() работал (или что-либо, что создает экземпляры, например ваш List[]), должен быть либо Int.__or__, либо List.__ror__, либо и то, и другое.
Это работает с классами, как в Int | List, потому что type сам реализует как __or__, так и __ror__:
>>> type.__or__
<slot wrapper '__or__' of 'type' objects>
Поэтому попробуйте добавить в свои классы следующие методы:
from typing import Union
class List:
...
def __or__(self, other):
return Union[self, other]
def __ror__(self, other):
return Union[self, other]
Кажется, хорошо работает с другими типами, даже с собственным классом:
>>> List() | List
typing.Union[<__main__.List object at 0x0000024A7F7F93D0>, __main__.List]
Дох! Нет, это не подтверждает очевидное. Я полностью пропустил это. Спасибо!
Я думаю, вы хотели бы поместить реализацию __or__ в метакласс. В противном случае вы рискуете переопределить желаемое поведение классов, в которых уже есть реализация оператора |, таких как целые числа и наборы. То есть вы хотите, чтобы Set() | Set([1, 2]) равнялось Set([1, 2]), а не Union[Set(), Set([1, 2])].
Все это совершенно неправильно, вы не должны реализовывать __or__ в классе, чтобы поддерживать использование класса в объединении для аннотации типа.
@juanpa.arrivillaga, это именно то, что делает класс _GenericAlias в модуле ввода Python. Это именно то, что вам нужно сделать...
@dvr Я хочу сказать, что тебе не следует этого делать. Почему вы определяете что-то, что действует как общий тип псевдонима?
@juanpa.arrivillaga, потому что я пишу фреймворк, который генерирует случайные аргументы для данной функции. Для этого я проверяю сигнатуру функции и использую аннотации типов для генерации аргументов. Первоначально я сделал это с типами Python, но обнаружил, что это проще реализовать, если я напишу свои собственные классы, каждый из которых имеет метод generate, отвечающий за создание переменной правильного типа.
Что касается вашего комментария выше, я согласен, что странно, что __class_getitem__ dunder создает экземпляр класса. Один из способов исправить это - иметь псевдоним для каждого типа, который я реализую. Я также мог бы просто использовать __init__ напрямую. Тем не менее, мне нравится тот факт, что я могу использовать синтаксис вроде List[Int | Str]. Аргументы, которые вы видите в Int, предназначены для «специализации» сгенерированного аргумента. Например, Int[0,10] означает, что сгенерированный аргумент будет целым числом от 0 до 10.
Буду рад и признателен за ваши мысли о том, как это сделать!
@dvr это имеет гораздо больше смысла, пожалуйста, подумайте о том, чтобы быть немного более явным в отношении такого рода контекста. Отмечу, вам, вероятно, не следует использовать __class_getitem__, его использование «не рекомендуется». Кроме того, вам, вероятно, следует использовать check out PEP 593, если вы хотите создавать свои собственные аннотации для использования во время выполнения и обеспечивать их совместимость с инструментами статического анализа! В противном случае вы просто создаете неверные аннотации из точки зрения инструментов статического анализа, таких как mypy, pyright
Давайте продолжим обсуждение в чате.
Какая версия Python и как выглядит набор текста для
pybt.typing.Int? Если вы используете более старую версию Python, я не удивлюсь, если быстрое обновление — это все, что вам нужно, чтобы это исправить.