【问题标题】:mypy updating return value from child method using child's name instead of parent's method's generic signaturemypy 使用子方法的名称而不是父方法的通用签名更新子方法的返回值
【发布时间】:2020-09-21 15:37:25
【问题描述】:

我有一个Generic 基类,它以一种方法(get_self)返回自身。我已经输入提示了。

然后我有一个该基类的子类,它为Generic 传入一个类型arg。在那个儿童班,我打电话给get_self。我想将类型提示更新为子类的名称。

但是,mypy==0.782 正在报告 error: Incompatible return value type (got "Foo[Bar]", expected "DFoo") [return-value]。有没有办法做到这一点?


**编辑**

经过进一步思考,我决定重新解释这个问题。对于冗长,请提前道歉。

  1. 基类 (Foo) 有一个方法 (get_self) 类型提示返回自身的实例
  2. 子类 (DFoo) 不会覆盖该方法
  3. 然后子类使用 (get_self) 方法
    • 并且知道返回类型实际上是子类 (DFoo)
  4. 但是,静态类型检查器(例如:mypy)不知道子类的方法实际上会返回子类的对象,因为它们使用来自基类的类型提示

因此,如果不使用新类型提示在子类中重新声明方法 (get_self),我的问题可能无法实现。

我可以将get_self 的返回值设为TypeVar。然而,由于基类Foo 已经是Generic,目前还不可能,因为它需要python/typing Higher-Kinded TypeVars #548 中提到的“Higher-Kinded TypeVars”。


示例脚本

我希望这能澄清我想要了解的内容。

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

【问题讨论】:

  • Foo[Bar] 不是一个类;这是一个类型提示。
  • 你是说Foo不应该继承Foo[Bar]吗?我的理解是当输入Foo[Bar]时,它是类Foo,其类型参数=Bar
  • 类不接受类型参数;类型提示。你应该只继承 Foo 本身。
  • 检查this mypy comment。为了特异性,可以在继承期间传递类型参数。如果我从继承中删除 [Bar],则错误变得不那么具体并且仍然存在:error: Incompatible return value type (got "Foo[Any]", expected "DFoo")。也许我遗漏了一些东西,但我不认为 DFoo 的类型 arg 在这种情况下会造成问题。
  • def do_something_get_self(self) -> DFoo: self.get_self() return self 建议就足够了;通过调用 super 的 get_self 然后返回 self 我们可以超越类型检查 @IntrastellarExplorer 你怎么看?

标签: python mypy static-typing python-typing


【解决方案1】:

要解决这个问题,只需将get_self 函数输入为def get_self(self: S) -> S,其中 S 是某种类型的变量。

然后下面的程序将干净地键入 check:

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

【讨论】:

  • 关键实际上是类型提示self: S,我花了一秒钟才意识到。再次感谢@Michael0x2a!在我的实际用例中,这解决了问题!
猜你喜欢
  • 1970-01-01
  • 2011-11-07
  • 1970-01-01
  • 2017-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多