如何将返回类型注释为类实例或其(唯一)子类实例?
我正在编写一个python库,该库是通过导入和(可选的)子类别提供的一些“助手类”所提供的。我无法提出一个可以正确允许静态分析工具正确识别我的“助手类”方法类型的设计。这是我遇到的问题(其中之一)的MWE:
我的lib
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
摘要没有警告静态分析工具,因为将事物检测到abor
,但在失败最后一行的运行时间是因为do_something_invalid()
尚未定义。难道是否可以提示Thingy
实际上是Bettering
的实例?
到目前为止,我的尝试是:
尝试1
Annotate contaerofthings._things
AS dict [str,thing,thing]
而不是dict> dict [str,thing,thing_cls]
这通过mypy be_civilized'for class'thing'thing''
的实例,因此抱怨“未解决的属性参考'
,但Pycharm将Thing
作为thing thing
()返回值为thick
不足为奇的是,这会触发pycharm和mypy的错误,涉及thick
没有“ be_civilized”属性。
尝试3
使用thingType = typeVar(“ ThingType”,Bound = Thing)
作为containerOfthings.get_thing()
我相信(?)的返回值
,这是什么> TypeVar
是为了使用Thing
的事实,并使用Betterthing
,以及<的每个返回值,代码> containerofthings.get_thing(),我的“真实”库将很麻烦。
有一个优雅的解决方案吗? get_unique_subclass()
太肮脏了,可以通过静态分析播放一个好的技巧吗?我无法提出的typing_extensions.protocol
感谢您的建议。
I am writing a python library used by importing and (optionally) sub-classing some of the 'helper classes' it provides. I fail to come up with a design that would properly let static analysis tools properly recognise the types that my 'helper classes' methods deal with. Here's a MWE illustrating (one of) the issues I run into:
My lib
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
)
What I expect users to do with it
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
This snippet does not alarm static analysis tools, because thingy is detected as Any
, but fails at runtime on the last line because do_something_invalid()
is not defined. Isn't it possible to give hints that thingy
is in fact an instance of BetterThing
here?
My attempts so far:
Attempt 1
Annotate ContainerOfThings._things
as Dict[str, Thing]
instead of Dict[str, thing_cls]
This passes mypy, but pycharm detects thingy
as an instance of Thing
and thus complains about "Unresolved attribute reference 'be_civilized' for class 'Thing'"
Attempt 2
Annotate ContainerOfThings.get_thing()
return value as Thing
Less surprisingly, this triggers errors from both pycharm and mypy about Thing
not having the 'be_civilized' attribute.
Attempt 3
Use ThingType = TypeVar("ThingType", bound=Thing)
as return value for ContainerOfThings.get_thing()
I believe (?) that this is what TypeVar
is intended for, and it works, except for the fact that mypy then requires thingy
to be be annotated with BetterThing
, along with every return value of ContainerOfThings.get_thing()
, which will be quite cumbersome with my 'real' library.
Is there an elegant solution for this? Is get_unique_subclass()
too dirty a trick to play nice with static analysis? Is there something clever to do with typing_extensions.Protocol
that I could not come up with?
Thanks for your suggestions.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
基本上,您需要
contarerofthings
是通用的:https://mypy.readthedocs.io.iredocs.io/en/stable/ generics.html#定义基因级
,然后我认为
contaerofthings
可以明确地对其生成的东西的类型显式而不是自动找到某些子sub,这会更好 - 已定义的类。我们可以将其放在一起以满足Mypy的方式(我也希望Pycharm,尽管我没有尝试过)...
Basically you need
ContainerOfThings
to be generic:https://mypy.readthedocs.io/en/stable/generics.html#defining-generic-classes
And then I think it would be better for
ContainerOfThings
to be explicit about the type of thing that it will generate instead of auto-magically locating some sub-class that has been defined.We can put this together in a way that will satisfy mypy (and I would expect pycharm too, though I haven't tried it)...