简体   繁体   English

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

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

Suppose I have a class that implements method chaining:假设我有一个实现方法链接的 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

I could use it like this:我可以这样使用它:

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

This works, but if I have a subclass M3D:这可行,但如果我有一个子类 M3D:

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

Now I can't do this:现在我不能这样做:

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

I get the following error in mypy:我在 mypy 中收到以下错误:

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

Because set_width() returns an M which has no method set_depth .因为set_width()返回一个没有方法set_depthM I have seen suggestions to override set_width() and set_height() for every subclass to specify the correct types, but that would be a lot of code to write for each method.我已经看到建议为每个子类覆盖set_width()set_height()以指定正确的类型,但这将是为每个方法编写的大量代码。 There has to be a easier way.必须有一个更简单的方法。

This is also relevant for special methods, for example __enter__ traditionally returns self , so it would be nice to have a way to specify this without needing to even mention it in subclasses.这也与特殊方法有关,例如__enter__传统上返回self ,因此最好有一种方法来指定它而无需在子类中提及它。

This is a classic problem in any language using inheritance.这是使用 inheritance 的任何语言中的经典问题。 And it is handled differently by the languages:语言的处理方式不同:

  • in C++, you would cast the result of set_height before calling set_depth在 C++ 中,您将在调用set_height之前set_depth的结果
  • in Java, you could either use the same casting as C++, or have the IDE to generate the bunch of overrides and only manually change the type in the overriden methods.在 Java 中,您可以使用与 C++ 相同的强制转换,或者使用 IDE 来生成一堆覆盖方法,并且只能手动更改覆盖方法中的类型。

Python is a dynamically typed language, so there is no cast instruction. Python 是动态类型语言,所以没有强制转换指令。 So you are left with 3 possible ways:所以你有3种可能的方式:

  • the brave way: override all the relevant methods to call the base method and declare the new type in the return annotation勇敢的方式:重写所有相关方法以调用基方法并在返回注释中声明新类型
  • the I don't care way: annotation control only gives warnings.我不在乎的方式:注释控制只给出警告。 As you know that the line is fine, you can just ignore the warning如您所知,这条线很好,您可以忽略警告
  • the don't bother me way: annotations are optional in Python and annotation control can normally be suspended by special comments.不要打扰我的方式:注释在 Python 中是可选的,注释控制通常可以通过特殊注释暂停。 Here you know that there is no problem, so you can safely suspend the type control for that instruction or that method.在这里知道没有问题,因此您可以安全地暂停该指令或该方法的类型控制。

The following is only my opinion.以下只是我的看法。

I would avoid the don't bother way if possible, because if you will leave warnings in your code, you would have to later control after each and every change if there is a new warning.如果可能的话,我会避免不打扰的方式,因为如果您将在代码中留下警告,那么如果有新的警告,您将不得不在每次更改后进行控制。

I would not override methods just to get rid of a warning.我不会仅仅为了摆脱警告而重写方法。 After all Python is a dynamically typed language that even allows duck typing.毕竟 Python 是一种动态类型语言,甚至允许鸭子类型。 If I know that the code is correct I would avoid adding useless code (DRY and KISS principles)如果我知道代码是正确的,我会避免添加无用的代码(DRY 和 KISS 原则)

SO I will just assume that comments to suspend annotation controls were invented for a reason and use them (what I call don't bother me here ).因此,我将假设暂停注释控件的注释是出于某种原因而发明的并使用它们(我所说的在这里不会打扰我)。

In Python 3.11 and its later versions, you will be able to do this:在 Python 3.11 及其更高版本中,您将能够执行此操作:

from typing import Self

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

After a lot of research and expirimentation, I have found a way that works in mypy, though Pycham still guesses the type wrong sometimes.经过大量的研究和实验,我找到了一种在 mypy 中有效的方法,尽管 Pycham 有时仍然会猜错类型。

The trick is to make self a type var:诀窍是使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

The last line specifically works fine in mypy but pycharm will still autocomplete set_depth sometimes even though .copy() actually returns an M even when called on a M3D .最后一行在 mypy 中特别有效,但set_depth有时仍会自动完成 set_depth ,即使.copy()实际上返回M ,即使在M3D上调用也是如此。

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

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