Ищу рабочий пример `SupportsRound`

В Интернете не так много подробной информации о том, как заставить аннотации типов работать с __round__. Я реализовал это, но все еще получаю сообщение об ошибке в строке 16 (вызов round без аргумента ndigits) при запуске mypy:

ошибка: несовместимые типы в присвоении (выражение имеет тип "int", переменная имеет тип "MyClass")

Тест проходит успешно, т.е. в обоих вызовах round я возвращаю объект типа MyClass. Но проверка MyPy терпит неудачу только тогда, когда я вызываю round без аргумента.

Номера версий: Python 3.6.5, mypy 0.641.

from typing import Any, SupportsRound, overload

class MyClass(SupportsRound['MyClass']):

    def __round__(self: 'MyClass', ndigits: int = 0) -> 'MyClass':
        return self


def test_tmp() -> None:
    x = MyClass()
    result: MyClass

    result = round(x, 0)
    assert type(result) == MyClass
    result = round(x)
    assert type(result) == MyClass

person migwellian    schedule 26.11.2018    source источник


Ответы (1)


Я считаю, что проблема здесь не столько в том, что вы используете SupportsRound, сколько в определении функции round. Функция round определена в typeshed, репозитории подсказок типов для стандартной библиотеки, чтобы иметь следующая подпись:

@overload
def round(number: float) -> int: ...
@overload
def round(number: float, ndigits: None) -> int: ...
@overload
def round(number: float, ndigits: int) -> float: ...
@overload
def round(number: SupportsRound[_T]) -> int: ...
@overload
def round(number: SupportsRound[_T], ndigits: None) -> int: ...  # type: ignore
@overload
def round(number: SupportsRound[_T], ndigits: int) -> _T: ...

Обратите внимание: если указан только один аргумент или ndigits имеет значение None, на выходе всегда будет int. Это соответствует документированному поведению функции round в стандартной библиотеке: https://docs.python.org/3/library/functions.html#round

К сожалению, я не вижу действительно чистого способа обойти это: я не думаю, что реализация SupportsRound действительно соответствует этому поведению.

В частности, SupportsRound, вероятно, следовало определить примерно так:

@runtime
class SupportsRound(Protocol[_T_co]):
    @abstractmethod
    @overload
    def __round__(self, ndigits: None = None) -> int: ...

    @abstractmethod
    @overload
    def __round__(self, ndigits: int) -> _T_co: ...

По сути, вынуждайте пользователя обрабатывать эти два случая.

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

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

person Michael0x2a    schedule 27.11.2018