Функция перегрузки с помощью SupportsInt, а не SupportsInt?

Я хочу перегрузить функцию ниже, чтобы, если передано значение, которое поддерживает int() подсказки типа Python int в противном случае тип Python подсказывает переданное значение.

Модуль Python typing предоставляет тип SupportsInt, который мы можем использовать, чтобы проверить, поддерживает ли наше значение int.

from typing import Any, SupportsInt, overload

@overload
def to_int(value: SupportsInt) -> int: ...

@overload
def to_int[T: NotSupportsInt???](value: T) -> T: ...

def to_int(value: Any) -> Any:
    try:
        return int(value)
    except TypeError:
        return value

Но как мы можем указать во втором утверждении overload все значения, которые не поддерживают int?

Привет, Мэтт! Система подсказок типов Python не предоставляет встроенного способа прямого выражения этой концепции. Но вы можете добиться того, чего хотите, используя комбинацию подсказок типов и универсальных типов.

dincer.unal 02.05.2024 09:15
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
1
71
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы можете реализовать так:

Union, чтобы разрешить SupportsInt или любой другой тип.

Используйте переменную типа T и Union, чтобы указать, что вторая перегрузка может принимать любой тип.

Во второй перегрузке мы указываем, что тип возвращаемого значения может быть либо int, либо того же типа, что и входное значение.

Таким образом, если значение поддерживает int, оно будет преобразовано в int, в противном случае значение будет возвращено как есть.


from typing import Any, SupportsInt, TypeVar, Union, overload

T = TypeVar('T')

@overload
def to_int(value: SupportsInt) -> int: ...

@overload
def to_int(value: T) -> T: ...

def to_int(value: Union[SupportsInt, T]) -> Union[int, T]:
    try:
        return int(value)
    except TypeError:
        return value



Проверка типов невозможна из-за перекрытия подписей с несовместимыми типами возвращаемых значений.
user2357112 02.05.2024 09:32
Ответ принят как подходящий

Типы дают позитивные обещания — они определяют, что может делать объект. Не то, что объект не может сделать.

Если у вас есть объект статического типа float, вы знаете, что этот объект поддерживает __int__, поэтому вы знаете, что он является членом типа SupportsInt.

Если у вас есть объект статического типа object, object не имеет метода __int__, но этот объект все равно может поддерживать __int__. Объектом может быть число с плавающей запятой или экземпляр какого-либо другого подкласса object, поддерживающего __int__. Вы не знаете, вернет ли передача to_int целое число.

Ваша вторая перегрузка пытается сказать: «Не поддерживает __int__? Тогда верните исходный тип». Он должен сказать: «Не знаю, поддерживает ли он __int__? Тогда, возможно, верните исходный тип или, возможно, вернете int». Вы бы сделали это с помощью переменной неограниченного типа и возвращаемого типа объединения, например:

from typing import overload, SupportsInt, TypeVar

T = TypeVar('T')

@overload
def to_int(value: SupportsInt) -> int: ...

@overload
def to_int(value: str) -> int: ...

@overload
def to_int(value: T) -> T | int: ...

def to_int(value):
    try:
        return int(value)
    except TypeError:
        return value

Обратите внимание, что я также добавил отдельную перегрузку для str. str на самом деле не поддерживает __int__, поэтому не попадает под перегрузку SupportsInt — вызовы типа int('35') обрабатываются специальным случаем в int.__new__.

И конечно, иногда у вас может возникнуть случай, подобный to_int([]), когда вы знаете конкретный тип объекта и знаете, что вызов int завершится неудачно. Но система типов не распространяет эту информацию так, как это необходимо для работы более конкретных аннотаций. Для средства проверки типов экземпляр list может быть экземпляром какого-то странного подкласса __int__, поддерживающего list, даже если человек, смотрящий на код, видит, что это не так.

К сожалению, система аннотаций типов Python в настоящее время не имеет хорошего способа указания отрицательных типов. Вот что вы можете сделать:

from typing import Any, SupportsInt, TypeVar, Union, overload, cast

T = TypeVar('T')

# Option 1: downside: slight runtime penalty
def to_int_option_1(value: T) -> Union[int, T]:
    try:
        return int(cast(SupportsInt, value))
    except TypeError:
        return value

# Option 2: downside: ignoring type information, you'll need to make sure it's not hiding any bugs
def to_int_option_2(value: T) -> Union[int, T]:
    try:
        return int(value)  # type: ignore
    except TypeError:
        return value

Это немного шире, чем тот тип, который вы ищете, но, вероятно, это придется сделать (учитывая, что перекрывающиеся подписи с несовместимыми типами возврата, как предложено в других ответах, не поддерживаются).

Это может сделать использование вашей функции немного неудобным, поскольку она не сообщает вам тип возвращаемого значения в зависимости от того, является ли аргумент SupportsInt или нет. Например:

foo = to_int('37')
reveal_type(foo) # str | int, expected: int
bar = to_int([37])
reveal_type(bar) # list[int] | int, expected: list[int]

... а это значит, что вам, возможно, придется вставить такие символы, как assert isinstance(foo, int) или typing.cast(list[int], bar), чтобы проверка типов работала правильно.

Вы можете добиться большего с перегрузкой, если правильно ее напишете — перекрытие сигнатур с совместимыми типами возвращаемых значений допустимо. Мой ответ перегружен пройти проверку типа.

user2357112 02.05.2024 10:04

Ваше может быть лучше с точки зрения документации, но с точки зрения проверки типов наши решения кажутся эквивалентными (например, reveal_type(to_int('37')) печатает str | int и reveal_type(to_int([37])) печатает list[int] | int)

Jasmijn 02.05.2024 10:55

Но reveal_type(to_int(3.5)) дает int для моей версии и int | float для вашей.

user2357112 02.05.2024 20:27

Я думаю, вы запутались, потому что str на самом деле не поддерживает __int__ - особые случаи конструктора intstr. (Если подумать, было бы неплохо добавить для этого отдельную перегрузку.)

user2357112 02.05.2024 20:29

А, я об этом не подумал, ты прав. Ваш ответ кажется наиболее близким к поведению, которое хочет Мэтт, и которое возможно с текущими функциями типизации mypy и python.

Jasmijn 02.05.2024 23:15

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