我正在编写一个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
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检测到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
的目的,它工作,除了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"