如何将 return 类型注释为 class 实例或其(唯一)子 class 实例?
How to annotate a return type as either a class instance or its (unique) subclass instance?
我正在编写一个 python 库,通过导入和(可选)子 classing 它提供的一些 'helper classes' 使用。我没有想出一种设计,可以让静态分析工具正确识别我的 'helper classes' 方法处理的类型。这是一个 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
的实例,因此抱怨“class 的未解析属性引用 'be_civilized' 'Thing'"
尝试 2
将ContainerOfThings.get_thing()
return值注释为Thing
不出所料,这会触发 pycharm 和 mypy 关于 Thing
没有 'be_civilized' 属性的错误。
尝试 3
使用 ThingType = TypeVar("ThingType", bound=Thing)
作为 ContainerOfThings.get_thing()
的 return 值
我相信(?)这就是 TypeVar
的目的,并且它有效,除了 mypy 然后需要 thingy
用 BetterThing
注释的事实,连同 ContainerOfThings.get_thing()
的每个 return 值,这对于我的 'real' 库来说会非常麻烦。
有没有优雅的解决方案? get_unique_subclass()
玩静态分析是不是太肮脏了? typing_extensions.Protocol
有什么我想不出的聪明办法吗?
感谢您的建议。
基本上你需要 ContainerOfThings
才能通用:
https://mypy.readthedocs.io/en/stable/generics.html#defining-generic-classes
然后我认为 ContainerOfThings
明确说明它将生成的事物的类型,而不是 auto-magically 定位一些已定义的 sub-class 会更好。
我们可以将它们组合在一起以满足 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"
我正在编写一个 python 库,通过导入和(可选)子 classing 它提供的一些 'helper classes' 使用。我没有想出一种设计,可以让静态分析工具正确识别我的 'helper classes' 方法处理的类型。这是一个 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
的实例,因此抱怨“class 的未解析属性引用 'be_civilized' 'Thing'"
尝试 2
将ContainerOfThings.get_thing()
return值注释为Thing
不出所料,这会触发 pycharm 和 mypy 关于 Thing
没有 'be_civilized' 属性的错误。
尝试 3
使用 ThingType = TypeVar("ThingType", bound=Thing)
作为 ContainerOfThings.get_thing()
我相信(?)这就是 TypeVar
的目的,并且它有效,除了 mypy 然后需要 thingy
用 BetterThing
注释的事实,连同 ContainerOfThings.get_thing()
的每个 return 值,这对于我的 'real' 库来说会非常麻烦。
有没有优雅的解决方案? get_unique_subclass()
玩静态分析是不是太肮脏了? typing_extensions.Protocol
有什么我想不出的聪明办法吗?
感谢您的建议。
基本上你需要 ContainerOfThings
才能通用:
https://mypy.readthedocs.io/en/stable/generics.html#defining-generic-classes
然后我认为 ContainerOfThings
明确说明它将生成的事物的类型,而不是 auto-magically 定位一些已定义的 sub-class 会更好。
我们可以将它们组合在一起以满足 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"