【问题标题】:Can you type hint overload a return type for a method based on an argument passed into the constructor?您可以根据传递给构造函数的参数键入提示重载方法的返回类型吗?
【发布时间】:2022-01-05 04:50:42
【问题描述】:

假设我有这样的课程:

class myclass:
    def __init__ (self, param1: Tuple[str,...], param2: bool) -> None:
        self.member1 = param1
        self.member2 = param2
        self.member3 = 10

    def gimmie(self) -> int | Tuple[str,...]:
        return self.member1 if self.member2 else self.member3

有什么方法可以确保来自 gimmie 的返回不是类型为int | Tuple[str,...],而是intTuple[str,...]? p>

编辑:

有几个答案需要大量的杂技才能做到这一点,而我真正想做的只是cast 回报。因此,这些答案中的每一个都对代码“气味”进行了评论。

问题只是我构造了一个带有标志的对象,其中一种方法根据该标志返回 2 种类型中的 1 种。如果那是糟糕的设计,那么“正确”的做法是什么?

【问题讨论】:

  • 你可以使用泛型并将构造函数包装在工厂函数中吗?
  • @Samwise 不知道,请详细说明。
  • 我认为您需要将返回类型绑定到类类型。因此,一种方法是让该类成为泛型,例如myclass[int]myclass[Tuple[str,...]]。另一种方式(也许我会走的方式)是拥有具有不同方法签名的子类。
  • @Samwise 我不熟悉泛型,我需要研究一下。对于后者,您将如何推迟返回子类?
  • 与您对@overload 所做的类似,为父类指定联合类型,并在子类中实现缩小范围。

标签: python python-3.x overloading type-hinting


【解决方案1】:

这是一种使用泛型解决此问题的方法:

from __future__ import annotations
from typing import overload, Literal, Generic, TypeVar, cast

T = TypeVar('T')


class myclass(Generic[T]):
    member1: tuple[str, ...]
    member2: bool
    member3: int

    @overload
    def __init__(self: myclass[tuple[str, ...]], param1: tuple[str, ...], param2: Literal[True]) -> None:
        ...

    @overload
    def __init__(self: myclass[int], param1: tuple[str, ...], param2: Literal[False]) -> None:
        ...

    def __init__(self, param1: tuple[str, ...], param2: bool) -> None:
        self.member1 = param1
        self.member2 = param2
        self.member3 = 10

    def gimmie(self) -> T:
        return cast(T, self.member1 if self.member2 else self.member3)


reveal_type(myclass(('a', 'b'), True).gimmie())
# note: Revealed type is "builtins.tuple*[builtins.str]"

reveal_type(myclass(('a', 'b'), False).gimmie())
# note: Revealed type is "builtins.int*"

一些注意事项:

  • 这种方法需要注释self 参数以赋予它不同的静态类型。通常情况下,我们不会注释self,所以请务必不要忘记这一点!
  • 遗憾的是,如果不添加 cast,我无法让 a if b else c 拥有正确的类型。

我同意 Samwise 的观点,这种类型的柔道是一种代码味道,并且可能隐藏了您项目设计的问题。

【讨论】:

  • 感谢您的回答,但我所缺少的只是有关 cast 功能的知识。我绝对不需要做任何疯狂的事情。不过,这里有一些关于泛型和 TypeVars 的有用信息,谢谢。
  • 请注意,这只有在member2 属性被认为是不可变的情况下才真正有效。 mypy 无法检测到您是否执行了类似obj = myclass(("foo", "bar"), True); obj.member2 = False; y: int = obj.gimmie() 的操作,并且会将此标记为错误,即使在运行时obj.gimmie 将返回int
  • @chepner 是的。我应该使用member2: Final[bool] 来捕捉它。
【解决方案2】:

这是使用子类和@overloaded 工厂函数来解决它的一种方法:

from typing import Literal, Tuple, Union, cast, overload


class MyClass:
    def __init__(self, param1: Tuple[str, ...], param2: bool) -> None:
        self.member1 = param1
        self.__member2 = param2
        self.member3 = 10

    def gimmie(self) -> Union[int, Tuple[str, ...]]:
        return self.member1 if self.__member2 else self.member3


class _MySubclass1(MyClass):
    def gimmie(self) -> Tuple[str, ...]:
        return cast(Tuple[str, ...], MyClass.gimmie(self))


class _MySubclass2(MyClass):
    def gimmie(self) -> int:
        return cast(int, MyClass.gimmie(self))


@overload
def myclass(param1: Tuple[str, ...], param2: Literal[True]) -> _MySubclass1:
    ...


@overload
def myclass(param1: Tuple[str, ...], param2: Literal[False]) -> _MySubclass2:
    ...


def myclass(param1: Tuple[str, ...], param2: bool) -> MyClass:
    if param2:
        return _MySubclass1(param1, param2)
    else:
        return _MySubclass2(param1, param2)


myobj1 = myclass((), True)
myobj2 = myclass((), False)
reveal_type(myobj1.gimmie())  # Revealed type is "builtins.tuple[builtins.str]"
reveal_type(myobj2.gimmie())  # Revealed type is "builtins.int"

请注意,这是一项大量工作,需要特别注意以确保 casts 与实现逻辑相匹配——我不知道您要解决的实际问题,但不得不去通过这么多的麻烦来正确排列打字通常是您建模数据的方式中的一种“气味”。

【讨论】:

  • 演员!这就是我真正需要的。我可以投射结果。这主要是我一直在寻找的。您在这里拥有的是 def 矫枉过正,不是我想要做的,我不需要工厂功能。现实世界的问题只是我在创建对象时设置了一个标志,然后它的一个方法可以根据该标志返回 2 个不同的结果。如果我开始将其视为一种或另一种,我只需要确保在返回类型上没有出现任何类型错误。谢谢!
  • 使用cast 的问题(以及你可能想把它埋在所有这些东西下面的原因)是它消除了在你使用它的时候进行类型检查的好处。如果您的界面用户需要cast 才能使用结果,那么您最好一开始就不要声明类型。
  • 我明白你在说什么,但我没有看到其他任何接近干净或直接的解决方案。该方法可以根据对象的构造方式简单地返回两种类型之一。而已。我不知道你会如何输入提示返回。除非我遗漏了一些东西,否则它似乎并不是那么不寻常或复杂的问题。需要明确的是,我构造对象的范围与接收结果的范围相同,因此它知道它将返回的类型,我只是在类型提示它是一致的。
  • 该方法具有不同的返回类型这一事实确实是问题的根源,这是我需要了解更多以提供更好解决方案的部分。这似乎更像是一个界面设计问题,而不是编码/打字问题。
  • 好的。该类是一个持续解析和监视另一个线程上另一个进程的标准输出的类。标准输出以 hrs:mins:secs 的形式持续提供当前进度,并通过管道传输到该类。我传递给构造函数的标志是是否以列表 [hrs, mins, secs] 或百分比形式返回该时间;即计算 hrs:mins:secs 为 (totalseconds/completeseconds)*100,也就是一个 int。只有当我调用请求它的方法时才会返回进度。
猜你喜欢
  • 1970-01-01
  • 2010-10-21
  • 2017-09-16
  • 1970-01-01
  • 2016-08-09
  • 2013-09-14
  • 1970-01-01
  • 2017-12-14
  • 1970-01-01
相关资源
最近更新 更多