如何将返回类型注释为类实例或其(唯一)子类实例?

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

我正在编写一个Python库,通过导入和(可选)子类化它提供的一些“帮助器类”来使用。我未能提出一种设计,可以正确地让静态分析工具正确识别我的“辅助类”方法处理的类型。这是一个 MWE,说明了我遇到的(其中一个)问题:

我的图书馆

from typing import Dict


class Thing:
    def shout(self):
        print(f"{self} says AAAAAAAAAaaaaaaaa")


class ContainerOfThings:
    def __init__(self):
        thing_cls = self._thing_cls = get_unique_subclass(Thing)
        self._things: Dict[str, thing_cls] = {}

    def add_thing(self, id_: str):
        self._things[id_] = self._thing_cls()

    def get_thing(self, id_: str):
        return self._things[id_]


def get_unique_subclass(cls):
    # this works but maybe there's a better way to do this?
    classes = cls.__subclasses__()
    if len(classes) == 0:
        return cls
    elif len(classes) == 1:
        return classes[0]
    elif len(classes) > 1:
        raise RuntimeError(
            "This class should only be subclassed once", cls, classes
        )

我希望用户用它做什么

class BetterThing(Thing):
    def be_civilized(self):
        print(f"{self} says howdy!")

container = ContainerOfThings()
container.add_thing("some_id")
thingy = container.get_thing("some_id")
thingy.be_civilized()
thingy.do_something_invalid()  # here I would like mypy to detect that this will not work

此代码片段不会向静态分析工具发出警报,因为 thingy 被检测为

Any
,但在运行时在最后一行失败,因为
do_something_invalid()
未定义。难道不能在这里暗示
thingy
实际上是
BetterThing
的一个实例吗?

到目前为止我的尝试:

尝试1

ContainerOfThings._things
注释为
Dict[str, Thing]
而不是
Dict[str, thing_cls]

这通过了 mypy,但 pycharm 检测到

thingy
作为
Thing
的实例,因此抱怨“类 'Thing' 的未解析属性引用 'be_civilized'”

尝试2

ContainerOfThings.get_thing()
返回值注释为
Thing

不足为奇的是,这会触发 pycharm 和 mypy 中关于

Thing
没有“be_civilized”属性的错误。

尝试3

使用

ThingType = TypeVar("ThingType", bound=Thing)
作为
ContainerOfThings.get_thing()

的返回值

我相信(?)这就是

TypeVar
的用途,并且它有效,除了 mypy 然后需要用
thingy
注释
BetterThing
以及
ContainerOfThings.get_thing()
的每个返回值,这对于我的“真正的”库来说会非常麻烦。

有一个优雅的解决方案吗?

get_unique_subclass()
是不是太肮脏了,无法很好地进行静态分析?有什么聪明的办法可以用
typing_extensions.Protocol
来做但我想不出吗?

感谢您的建议。

python python-typing mypy
1个回答
1
投票

基本上你需要

ContainerOfThings
才能通用:
https://mypy.readthedocs.io/en/stable/generics.html#defining-generic-classes

然后我认为

ContainerOfThings
最好明确它将生成的事物的类型,而不是自动神奇地定位已定义的某些子类。

我们可以用一种满足 mypy 的方式将其组合在一起(我也希望使用 pycharm,尽管我还没有尝试过)...

from typing import Dict, Generic, Type, TypeVar


class Thing:
    def shout(self):
        print(f"{self} says AAAAAAAAAaaaaaaaa")


T = TypeVar('T', bound=Thing)


class ContainerOfThings(Generic[T]):
    def __init__(self, thing_cls: Type[T]):
        self._thing_cls = thing_cls
        self._things: Dict[str, T] = {}

    def add_thing(self, id_: str):
        self._things[id_] = self._thing_cls()

    def get_thing(self, id_: str) -> T:
        return self._things[id_]


class BetterThing(Thing):
    def be_civilized(self):
        print(f"{self} says howdy!")
        

container = ContainerOfThings(BetterThing)
container.add_thing("some_id")
thingy = container.get_thing("some_id")
thingy.be_civilized()  # OK
thingy.do_something_invalid()  # error: "BetterThing" has no attribute "do_something_invalid"
© www.soinside.com 2019 - 2024. All rights reserved.