繁体   English   中英

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

[英]Python how to type anotate a method that returns self?

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

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

我可以这样使用它:

box = M().set_width(5).set_height(10)

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

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

现在我不能这样做:

cube = M3D().set_width(2).set_height(3).set_depth(5)

我在 mypy 中收到以下错误:

_test_typeanotations.py:21: error: "M" has no attribute "set_depth"; maybe "set_width"

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

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

这是使用 inheritance 的任何语言中的经典问题。 语言的处理方式不同:

  • 在 C++ 中,您将在调用set_height之前set_depth的结果
  • 在 Java 中,您可以使用与 C++ 相同的强制转换,或者使用 IDE 来生成一堆覆盖方法,并且只能手动更改覆盖方法中的类型。

Python 是动态类型语言,所以没有强制转换指令。 所以你有3种可能的方式:

  • 勇敢的方式:重写所有相关方法以调用基方法并在返回注释中声明新类型
  • 我不在乎的方式:注释控制只给出警告。 如您所知,这条线很好,您可以忽略警告
  • 不要打扰我的方式:注释在 Python 中是可选的,注释控制通常可以通过特殊注释暂停。 在这里知道没有问题,因此您可以安全地暂停该指令或该方法的类型控制。

以下只是我的看法。

如果可能的话,我会避免不打扰的方式,因为如果您将在代码中留下警告,那么如果有新的警告,您将不得不在每次更改后进行控制。

我不会仅仅为了摆脱警告而重写方法。 毕竟 Python 是一种动态类型语言,甚至允许鸭子类型。 如果我知道代码是正确的,我会避免添加无用的代码(DRY 和 KISS 原则)

因此,我将假设暂停注释控件的注释是出于某种原因而发明的并使用它们(我所说的在这里不会打扰我)。

在 Python 3.11 及其更高版本中,您将能够执行此操作:

from typing import Self

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

经过大量的研究和实验,我找到了一种在 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

最后一行在 mypy 中特别有效,但set_depth有时仍会自动完成 set_depth ,即使.copy()实际上返回M ,即使在M3D上调用也是如此。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM