基于子类的基类方法的类型提示

Posted

技术标签:

【中文标题】基于子类的基类方法的类型提示【英文标题】:Type hint of Base class method based on Subclass 【发布时间】:2022-01-03 16:42:06 【问题描述】:

当创建一个基类时,其请求方法对所有子类都完全相同。似乎没有一种简单的方法可以根据发出请求的子类来提供单独的类型提示。

下面的例子是我目前解决这个问题的方法,但似乎有更好的方法。

class Response(TypeDict): ...

class FooResponse(Response): ...

class BarResponse(Response): ...

class Request:

    @classmethod
    def make_request(cls, args: Any) -> Response:
        # This will return a dict response based on cls.TYPE (if cls.TYPE == Foo, FooResponse will be returned)
        return execute_db_query(cls, args)


class FooRequest(Request):

    @classmethod
    def make_request(cls, args: Any) -> FooResponse:
        return FooResponse(**super().make_request(cls, args))


class BarRequest(Request):

    @classmethod
    def make_request(cls, args: Any) -> BarResponse:
        return BarResponse(**super().make_request(cls, args))

是否有更好的方法来做到这一点,要么通过在子类上指定响应类型,要么只覆盖方法签名而不覆盖功能?

类似的东西(我知道这不起作用):

class FooRequest(Request):
    @classmethod
    def make_request(...) -> FooResponse: ...

我认为使用像 Generic[T] 和 TypeVar() 这样的东西可能是一种方法吗?

【问题讨论】:

请编辑问题以将其限制为具有足够详细信息的特定问题,以确定适当的答案。 【参考方案1】:

也许typing.overload 就是你要找的。​​p>

from __future__ import annotations
from typing import overload

class Request:
    @overload
    @classmethod
    def make_request(cls: FooRequest, args: Any) -> FooResponse: ...
    @overload
    @classmethod
    def make_request(cls: BarRequest, args: Any) -> BarResponse: ...
    
    def make_request(cls, args):
        return actual_implementation(cls, args)

现在您的FooRequestBarRequest 不再需要提供make_request 的实现(或键入提示)。


另一方面,如果您的唯一目标是在 IDE 中输入提示,您可以:

from __future__ import annotations
from typing import TypeVar


class Response: ...
class FooResponse(Response): ...
class BarResponse(Response): ...

T = TypeVar('T', bound=Response, covariant=True)

class Request:
    @classmethod
    def make_request(cls: Request, args: Any) -> T: 
          return actual_implementation(cls, args


class FooRequest(Request):
    @classmethod
    def make_request(cls: FooRequest, args: Any) -> FooResponse:
          return super(FooRequest, cls).make_request(args)

这将使 IDE 接受

fooresponse: FooResponse = FooRequest.make_request(None)

但拒绝

fooresponse: FooResponse = Request.make_request(None)

【讨论】:

有没有办法更动态地做到这一点?想象一下,如果有 10 个子类,那么您会有很长的重载列表使基类混乱 动态地,没有。在任何情况下,您的 actual_implementation 都需要进行案例管理才能返回不同的类型。否则,您可以说基本方法返回基本响应的协变类型 T 上限,但我真的无法想象 MRE 的用例。 实际上,在运行时,所有的响应都是 Dict 类型的,并且类型之间是无法区分的。你可能已经知道,TypeDict 仅用于类型提示。我不认为我理解你关于协变类型的第二个陈述。 检查编辑,如果您只关心 IDE 提示,这可能更接近您的用例。 ps:你可以完全放弃协变TypeVar,只让基本方法返回Response

以上是关于基于子类的基类方法的类型提示的主要内容,如果未能解决你的问题,请参考以下文章

从 Typescript 中的基类创建子类的新实例 [重复]

为啥我不能从扩展类型的基类调用扩展方法?

如果我在 package1 中有带有受保护方法的基类

c ++使基类在子类中使用覆盖的方法

从 Java 中的基类访问子类字段

Java 工厂方法模式的简单示例