mypy обновляет возвращаемое значение из дочернего метода с использованием имени ребенка вместо общей подписи родительского метода

У меня есть базовый класс Generic, который возвращает себя одним методом (get_self). Я типа намекнул как таковой.

Затем у меня есть дочерний класс этого базового класса, который передает аргумент типа для Generic. В этом дочернем классе я вызываю get_self. Я хотел бы обновить подсказку типа, чтобы она была просто именем дочернего класса.

Однако mypy==0.782 сообщает о error: Incompatible return value type (got "Foo[Bar]", expected "DFoo") [return-value]. Есть ли способ добиться этого?


** Изменить **

Я решил повторно объяснить вопрос после дальнейшего размышления. Заранее извиняюсь за многословие.

  1. Базовый класс (Foo) имеет тип метода (get_self), на который намекают, чтобы он возвращал экземпляр самого себя
  2. Дочерний класс (DFoo) не отменяет метод
  3. Child class then uses the (get_self) method
    • And knows that the return type will actually be of the child class (DFoo)
  4. Однако средства проверки статического типа (например, mypy) не знают, что метод дочернего класса фактически вернет объект дочернего класса, поскольку они используют подсказку типа из базового класса.

Поэтому мой вопрос может быть невозможен без повторного объявления метода (get_self) в дочернем классе с подсказкой нового типа.

Я мог бы сделать возвращение get_self TypeVar. Однако, поскольку базовый класс Foo уже является Generic, в настоящее время это невозможно, поскольку для этого потребуются высокопроизводительные TypeVars, упомянутые в python / typing Higher-Kinded TypeVars # 548.


Образец сценария

Я надеюсь, что это проясняет то, что я пытаюсь понять.

from __future__ import annotations

from typing import Generic, TypeVar, cast

T = TypeVar("T")

class Foo(Generic[T]):
    def get_self(self) -> Foo[T]:
        # Other stuff happens here before the return
        return self

class Bar:
    pass

class DFoo(Foo[Bar]):
    def do_something_get_self(self) -> DFoo:
        # mypy error: Incompatible return value type (got "Foo[Bar]", 
        # expected "DFoo")
        return self.get_self()

class DFooCast(Foo[Bar]):
    def do_something_get_self(self) -> DFooCast:
        # This works, but I don't like this method. I don't want to use `cast`
        # all over the place.
        return cast(DFooCast, self.get_self())

class DFooNoUpdatedTypeHint(Foo[Bar]):
    def do_something_get_self(self) -> Foo[Bar]:
        # mypy doesn't error here, but later on it will raise an error 
        # when using method's added in Foo subclasses
        return self.get_self()

    def dfoo_adds_method(self) -> None:
        """DFoo also has additional methods."""

dfoo = DFooNoUpdatedTypeHint()
dfoo.do_something_get_self().dfoo_adds_method()  # error: "Foo[Bar]" has no attribute "dfoo_adds_method"

И вот полный вывод mypy:

path/to/ret_type_type_t_subclass.py: note: In member "do_something_get_self" of class "DFoo":
path/to/ret_type_type_t_subclass.py: error: Incompatible return value type (got "Foo[Bar]", expected "DFoo")  [return-value]
path/to/ret_type_type_t_subclass.py: note: At top level:
path/to/ret_type_type_t_subclass.py: error: "Foo[Bar]" has no attribute "dfoo_adds_method"  [attr-defined]

Версии

Python==3.8.5
mypy==0.782

person Intrastellar Explorer    schedule 21.09.2020    source источник
comment
Foo[Bar] не класс; это подсказка типа.   -  person chepner    schedule 21.09.2020
comment
Вы говорите, что Foo не должен быть подклассом Foo[Bar]? Насколько я понимаю, при вводе Foo[Bar] это класс Foo с его аргументом типа = Bar.   -  person Intrastellar Explorer    schedule 21.09.2020
comment
Классы не принимают аргумент типа; типа подсказки делаю. Вы должны наследовать только от самого Foo.   -  person chepner    schedule 21.09.2020
comment
Проверьте этот комментарий mypy. Для конкретности во время наследования можно передавать аргументы типа. Если я избавлюсь от [Bar] из наследования, ошибка станет менее конкретной и останется присутствующей: error: Incompatible return value type (got "Foo[Any]", expected "DFoo"). Возможно, мне что-то не хватает, но я не думаю, что аргумент типа для DFoo создает проблему в данном случае.   -  person Intrastellar Explorer    schedule 21.09.2020
comment
@IntrastellarExplorer Как насчет def do_something_get_self(self) -> Foo? Он элегантен, полиморфен, не требует сложных определений или назначений. Кроме того, он точно описывает возвращаемый тип.   -  person Aviv Yaniv    schedule 23.09.2020
comment
Хороший вопрос @AvivYaniv и спасибо за вопрос. Это потому, что подкласс DFoo добавляет методы, и без указания типа DFoo mypy позже вызовет ошибку. Я обновил вопрос, включив в него это   -  person Intrastellar Explorer    schedule 23.09.2020
comment
Что ж, если я возьму код как есть, учитывая, что существует супер-метод, который действует на объект и возвращает self, и мы хотим, чтобы в нашем классе была другая функция, которая активирует супер-метод и также возвращает self; мы можем сделать что-то вроде этого: def do_something_get_self(self) -> DFoo: self.get_self() return self и избежать проблемы с подсказкой типа супер get_self.   -  person Aviv Yaniv    schedule 23.09.2020
comment
Это не очень распространенный сценарий, поэтому, возможно, это ошибка в mypy. Я бы порекомендовал открыть проблему в их системе отслеживания проблем. Даже если это не ошибка, это лучшее место, где можно получить совет по этому поводу.   -  person yedpodtrzitko    schedule 24.09.2020
comment
@IntrastellarExplorer Я уверен, что вы сделаете упрощения, чтобы сделать пример удобочитаемым и в контексте, чтобы мы могли его понять. Буду рад услышать ваши мысли по поводу моего предложения, а также если возникнут какие-либо осложнения :)   -  person Aviv Yaniv    schedule 24.09.2020
comment
Хорошо, я обновил вопрос с более подробным объяснением проблемы @AvivYaniv. Дайте мне знать, если вы что-нибудь придумаете, но я думаю, что мне, возможно, придется пересмотреть общий дизайн, чтобы избежать проблемы, с которой я сталкиваюсь.   -  person Intrastellar Explorer    schedule 25.09.2020
comment
Предложение def do_something_get_self(self) -> DFoo: self.get_self() return self должно быть достаточным; вызывая get_self и возвращая self, мы можем обойти проверку типов @IntrastellarExplorer, что вы думаете?   -  person Aviv Yaniv    schedule 25.09.2020
comment
Да, @AvivYaniv, это жизнеспособное решение проблемы. Однако суть get_self в родительском классе Foo заключалась в том, чтобы его можно было использовать для получения собственного экземпляра, а не обходить стороной только потому, что mypy вызвал ошибку. Если вы видите выбранный ответ, это позволяет напрямую return self.get_self().   -  person Intrastellar Explorer    schedule 26.09.2020


Ответы (1)


Чтобы решить эту проблему, просто введите get_self функцию как def get_self(self: S) -> S, где S - это некоторый тип var.

Следующая программа выполнит чистую проверку:

from __future__ import annotations

from typing import Generic, TypeVar, cast

T = TypeVar("T")

# This can also be just 'S = TypeVar("S")', but that would mean
# we won't be able to use any methods of Foo inside of get_self.
S = TypeVar("S", bound="Foo")


class Foo(Generic[T]):
    def get_self(self: S) -> S:
        return self

class Bar:
    pass


class DFoo(Foo[Bar]):
    def do_something_get_self(self) -> DFoo:
        return self.get_self()

    def dfoo_adds_method(self) -> None:
        pass


dfoo = DFoo()
dfoo.do_something_get_self().dfoo_adds_method()

Причина, по которой это работает, заключается в том, что всегда можно переопределить тип по умолчанию self. Хотя обычно ему автоматически присваивается тип текущего класса, PEP 484 на самом деле не требует, чтобы вы придерживались этого значения по умолчанию.

Поэтому вместо этого мы делаем его универсальным, чтобы тип вывода всегда соответствовал текущему подтипу.

См. https://mypy.readthedocs.io/en/stable/generics.html#generic-methods-and-generic-self для получения дополнительных сведений об этом взаимодействии.

person Michael0x2a    schedule 25.09.2020
comment
Ключ на самом деле - это подсказка типа self: S, на осознание которой мне потребовалась секунда. И еще раз спасибо @ Michael0x2a! В моем случае это решило проблему! - person Intrastellar Explorer; 26.09.2020