静态类型检查器和语言服务器无法识别子类对象的属性

问题描述 投票:0回答:1

如果标题看起来有点复杂,我很抱歉,要表达这个问题有点困难。

假设我正在构建一个带有节点和边的图。它有一个基类

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
    得到了相同的消息。

我期望一个解决方案能够成功进行静态检查分析,并可能被语言服务器识别为有效属性。

python python-typing mypy python-language-server
1个回答
1
投票

正如 @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
识别为有效属性。

© www.soinside.com 2019 - 2024. All rights reserved.