Python如何键入一个返回自我的方法?

mou*_*ail 5 python type-hinting python-3.9

假设我有一个实现方法链的类:

from __future__ import annotations

class M:
    def set_width(self, width: int)->M:
        self.width = width
        return self

    def set_height(self, height: int)->M:
        self.height = height
        return self
Run Code Online (Sandbox Code Playgroud)

我可以这样使用它:

box = M().set_width(5).set_height(10)
Run Code Online (Sandbox Code Playgroud)

这有效,但如果我有一个子类 M3D:

class M3D(M):
    def set_depth(self, depth: int) -> M3D:
        self.depth = depth
        return self
Run Code Online (Sandbox Code Playgroud)

现在我不能这样做:

cube = M3D().set_width(2).set_height(3).set_depth(5)
Run Code Online (Sandbox Code Playgroud)

我在 mypy 中收到以下错误:

_test_typeanotations.py:21: error: "M" has no attribute "set_depth"; maybe "set_width"
Run Code Online (Sandbox Code Playgroud)

因为set_width()返回一个M没有方法set_depth。我已经看到建议覆盖set_width()set_height()为每个子类指定正确的类型,但是为每个方法编写大量代码。必须有更简单的方法。

这也与特殊方法相关,例如__enter__传统上的返回self,因此最好有一种方法来指定它,而无需在子类中提及它。

Boš*_*jak 14

从 Python 3.11 开始,您可以执行以下操作:

from typing import Self

class M:
    def set_width(self, width: int) -> Self:
        self.width = width
        return self
Run Code Online (Sandbox Code Playgroud)

  • 对于使用旧版本 Python 的用户,您可以安装 `typing-extensions` 包并导入 `Self`,例如:`fromtyping_extensions import Self` (6认同)

mou*_*ail 6

经过大量的研究和实验,我找到了一种在 mypy 中有效的方法,尽管 Pycham 有时仍然会猜测类型错误。

诀窍是创建self一个 var 类型:

from __future__ import annotations

import asyncio
from typing import TypeVar

T = TypeVar('T')


class M:
    def set_width(self: T, width: int)->T:
        self.width = width
        return self

    def set_height(self: T, height: int)->T:
        self.height = height
        return self

    def copy(self)->M:
        return M().set_width(self.width).set_height(self.height)


class M3D(M):
    def set_depth(self: T, depth: int) -> T:
        self.depth = depth
        return self

box = M().set_width(5).set_height(10) # box has correct type
cube = M3D().set_width(2).set_height(3).set_depth(5) # cube has correct type
attemptToTreatBoxAsCube = M3D().copy().set_depth(4) # Mypy gets angry as expected
Run Code Online (Sandbox Code Playgroud)

最后一行在 mypy 中工作得很好,但 pycharmset_depth有时仍然会自动.copy()完成,M即使在M3D.