如果标题看起来有点复杂,我很抱歉,要表达这个问题有点困难。
假设我正在构建一个带有节点和边的图。它有一个基类
Node
,具有所有节点的通用属性,并且每个特定的节点类型都可以继承它并扩展更多属性。
以以下两种节点类型为例:
from typing import TypeVar
# nodes.py
class Node:
def __init__(self, base_param: str) -> None:
self.base_param = base_param
# other base methods...
class NodeType1(Node):
def __init__(self, base_param: str, node_param_type1: int) -> None:
super().__init__(base_param)
self.node_param_type1 = node_param_type1
# other type1 methods...
class NodeType2(Node):
def __init__(self, base_param: str, node_param_type2: int) -> None:
super().__init__(base_param)
self.node_param_type2 = node_param_type2
# other type2 methods...
NodeSubClassType = TypeVar("NodeSubClassType", bound=Node)
它还有一个
Edge
基类,其中包含 source
和 target
,它们都是 Node
的子类。以下面为例,有一条边将 NodeType1
连接到 NodeType2
。
# edges.py
class Edge:
def __init__(self, source: NodeSubClassType, target: NodeSubClassType) -> None:
self.source = source
self.target = target
# other base methods...
class EdgeType1ToType2(Edge):
# Must contain `NodeType1` as `source` and `NodeType2` as `target` when instantiated
pass
# or add other `type1 to type2` methods
我的问题是,每当我创建
EdgeType1ToType2
的实例并传递正确的对象时,只有 base_param
和 source
的基本属性 target
才能被静态检查器识别(我尝试过 mypy
)和语言服务器(我尝试过pylsp
)。例如,在以下脚本中:
if __name__ == "__main__":
node_type1 = NodeType1("base_param", 1)
node_type2 = NodeType2("base_param", 2)
edge_12 = EdgeType1ToType2(source=node_type1, target=node_type2)
node_type1.base_param # this has no issue
edge_12.source.base_param # this has no issue
node_type1.node_param_type1 # this has no issue
edge_12.source.node_param_type1 # this is not recognized by `mypy` or the LSP
如果我运行
mypy
我会收到消息
error: "NodeSubClassType" has no attribute "node_param_type1" [attr-defined]
Found 1 error in 1 file (checked 1 source file)
仅适用于
edge_12.source.node_param_type1
线。此外,语言服务器无法将 node_param_type1
识别为 edge_12.source
的属性。与edge_12.target
类似的问题。
我尝试过:
添加多余的
__init__
将适当的参数传递给子类,例如
class EdgeType1ToType2(Edge):
def __init__(self, source: NodeType1, target: NodeType2) -> None:
super().__init__(source, target)
但它不起作用。
并直接用超类
NodeSubClassType
替换自定义类型Node
,但我用Node
得到了相同的消息。
我期望一个解决方案能够成功进行静态检查分析,并可能被语言服务器识别为有效属性。
正如 @STerliakov 评论的那样,一种可能的解决方案是创建两种显式节点类型,一种用作边缘的源,另一种用作边缘的目标:
_Source = TypeVar("_Source", bound=Node)
_Target = TypeVar("_Target", bound=Node)
然后创建基本边缘类,如下所示:
from typing import Generic
class Edge(Generic[_Source, _Target]):
def __init__(self, source: _Source, target: _Target) -> None:
self.source = source
self.target = target
# other base methods...
然后,应该通过显式传递所需的节点类型来创建具体的子类:
class EdgeType1ToType2(Edge[NodeType1, NodeType2]):
# Must contain `NodeType1` as `source` and `NodeType2` as `target` when instantiated
pass
然后,当使用原始脚本时
if __name__ == "__main__":
node_type1 = NodeType1("base_param", 1)
node_type2 = NodeType2("base_param", 2)
edge_12 = EdgeType1ToType2(source=node_type1, target=node_type2)
node_type1.base_param # this has no issue
edge_12.source.base_param # this has no issue
node_type1.node_param_type1 # this has no issue
edge_12.source.node_param_type1 # this has no issue
mypy
不再显示任何问题,并且当输入 edge_12.source.
时,编辑器会将 node_param_type1
识别为有效属性。