Python 如何键入注释返回 self 的方法?

问题描述 投票:0回答:3

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

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()
返回一个没有方法
M
set_depth
。我已经看到建议为每个子类重写
set_width()
set_height()
以指定正确的类型,但这将为每个方法编写大量代码。必须有一个更简单的方法。

这也与特殊方法相关,例如

__enter__
传统上返回
self
,因此最好有一种方法来指定它,甚至不需要在子类中提及它。

python type-hinting python-3.9
3个回答
13
投票

在 Python 3.11 及更高版本中,您可以执行以下操作:

from typing import Self

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

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

最后一行在 mypy 中工作得很好,但 pycharm 有时仍会自动完成

set_depth
,即使
.copy()
实际上返回
M
,即使在
M3D
上调用时也是如此。


2
投票

这是任何使用继承的语言中的一个经典问题。不同语言的处理方式也不同:

  • 在 C++ 中,您可以在调用
    set_height
     之前转换 
    set_depth
  • 的结果
  • 在 Java 中,您可以使用与 C++ 相同的转换,或者让 IDE 生成一堆重写,并且仅手动更改重写方法中的类型。

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

  • 勇敢的方法:重写所有相关方法来调用基方法并在返回注释中声明新类型
  • “我不在乎”方式:注释控件仅给出警告。正如您所知,线路没问题,您可以忽略警告 别打扰我
  • 方式:注释在Python中是可选的,注释控制通常可以通过特殊注释来暂停。这里
  • 知道没有问题,所以你可以安全地暂停对该指令或该方法的类型控制。 以下仅代表我的观点。
如果可能的话,我会避免使用“不打扰”的方式,因为如果你在代码中留下警告,那么如果有新的警告,你将不得不在每次更改后进行控制。

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

所以我只是假设暂停注释控件的注释是出于某种原因而发明的并使用它们(我称之为不要在这里打扰我

)。

© www.soinside.com 2019 - 2024. All rights reserved.