Типы объединений Python с объектами

Я пишу свои собственные классы для 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 и как выглядит набор текста для pybt.typing.Int? Если вы используете более старую версию Python, я не удивлюсь, если быстрое обновление — это все, что вам нужно, чтобы это исправить.

Samwise 24.04.2023 22:11

Это Python 3.11.2. Я не уверен, что понял ваш второй вопрос. Не могли бы вы уточнить? Спасибо!

dvr 24.04.2023 22:13

В частности, является ли Int подклассом typing.Generic или каким-либо другим типом с подпиской? Я не совсем уверен, как именно это работает во время выполнения с оператором |, но обычно это требование для проверки типов, поэтому я предполагаю, что это может быть уместно здесь.

Samwise 24.04.2023 22:24

Ах, нет, это не подкласс typing.Generic. Это свой собственный класс.

dvr 24.04.2023 22:42

См. мое редактирование для определения класса...

dvr 24.04.2023 22:47

это действительно странно, почему __class_getitem__ создает экземпляр класса!?

juanpa.arrivillaga 25.04.2023 01:29

"с генераторами на них". что вы подразумеваете под "генераторами"? «Я использую это для фаззинга функции на основе аннотаций типа». «Что это значит?»

juanpa.arrivillaga 25.04.2023 01:31
assert type(x) in [int, list, str, dict, list] не пройдет за ваши типы....
juanpa.arrivillaga 25.04.2023 01:32

«Похоже, это потому, что Int[0,10] имеет тип своего класса pybt.typing.Int, а не тип. Однако использование typing.Union работает просто». Конечно, потому что Union не требует для работы какого-либо специального метода. . Вы можете предоставить все, что хотите

juanpa.arrivillaga 25.04.2023 01:33
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
9
67
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы уже знаете о методах 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]

Дох! Нет, это не подтверждает очевидное. Я полностью пропустил это. Спасибо!

dvr 24.04.2023 23:04

Я думаю, вы хотели бы поместить реализацию __or__ в метакласс. В противном случае вы рискуете переопределить желаемое поведение классов, в которых уже есть реализация оператора |, таких как целые числа и наборы. То есть вы хотите, чтобы Set() | Set([1, 2]) равнялось Set([1, 2]), а не Union[Set(), Set([1, 2])].

Dunes 24.04.2023 23:20

Все это совершенно неправильно, вы не должны реализовывать __or__ в классе, чтобы поддерживать использование класса в объединении для аннотации типа.

juanpa.arrivillaga 25.04.2023 01:31

@juanpa.arrivillaga, это именно то, что делает класс _GenericAlias ​​в модуле ввода Python. Это именно то, что вам нужно сделать...

dvr 25.04.2023 02:08

@dvr Я хочу сказать, что тебе не следует этого делать. Почему вы определяете что-то, что действует как общий тип псевдонима?

juanpa.arrivillaga 25.04.2023 03:23

@juanpa.arrivillaga, потому что я пишу фреймворк, который генерирует случайные аргументы для данной функции. Для этого я проверяю сигнатуру функции и использую аннотации типов для генерации аргументов. Первоначально я сделал это с типами Python, но обнаружил, что это проще реализовать, если я напишу свои собственные классы, каждый из которых имеет метод generate, отвечающий за создание переменной правильного типа.

dvr 25.04.2023 04:31

Что касается вашего комментария выше, я согласен, что странно, что __class_getitem__ dunder создает экземпляр класса. Один из способов исправить это - иметь псевдоним для каждого типа, который я реализую. Я также мог бы просто использовать __init__ напрямую. Тем не менее, мне нравится тот факт, что я могу использовать синтаксис вроде List[Int | Str]. Аргументы, которые вы видите в Int, предназначены для «специализации» сгенерированного аргумента. Например, Int[0,10] означает, что сгенерированный аргумент будет целым числом от 0 до 10.

dvr 25.04.2023 04:34

Буду рад и признателен за ваши мысли о том, как это сделать!

dvr 25.04.2023 04:35

@dvr это имеет гораздо больше смысла, пожалуйста, подумайте о том, чтобы быть немного более явным в отношении такого рода контекста. Отмечу, вам, вероятно, не следует использовать __class_getitem__, его использование «не рекомендуется». Кроме того, вам, вероятно, следует использовать check out PEP 593, если вы хотите создавать свои собственные аннотации для использования во время выполнения и обеспечивать их совместимость с инструментами статического анализа! В противном случае вы просто создаете неверные аннотации из точки зрения инструментов статического анализа, таких как mypy, pyright

juanpa.arrivillaga 25.04.2023 20:00

Давайте продолжим обсуждение в чате.

dvr 26.04.2023 00:45

Другие вопросы по теме