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



我正在编写一个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的一个实例?

My attempts so far:

尝试1

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

这通过了mymyy,但pycharm检测到thingyThing的实例,因此抱怨类'Thing'的'未解析属性引用'be_civilized' '

尝试2

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

不足为奇的是,这会触发pycharm和mypy关于Thing没有'be_civilized'属性的错误。

尝试3

使用ThingType = TypeVar("ThingType", bound=Thing)作为ContainerOfThings.get_thing()的返回值

我相信(?)这就是TypeVar的目的,它工作,除了myypy然后需要用BetterThing注释thingy,以及ContainerOfThings.get_thing()的每个返回值,这对于我的"真正的"库来说将是相当麻烦的。

是否有一个优雅的解决方案?get_unique_subclass()是不是太脏了,不能很好地进行静态分析?typing_extensions.Protocol有什么聪明的地方是我想不到的吗?

谢谢你的建议。

基本上你需要ContainerOfThings是通用的:
https://mypy.readthedocs.io/en/stable/generics.html#defining-generic-classes

然后我认为ContainerOfThings最好明确它将生成的东西的类型,而不是自动神奇地定位一些已经定义的子类。

我们可以把这些放在一起,将满足我的方式(我也希望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"

最新更新