有一个基础数据类如下:
class BaseClass:
def __init__(self, attribute_1: Any):
self.attribute_1 = attribute_1
有一个使用上述类作为基类的继承数据类:
class DataClass(BaseClass):
def __init__(self, attribute_1: Any, attribute_2: Dict[str, str], attribute_3: List[str]):
super().__init__(attribute_1)
self.attribute_2 = attribute_2
self.attribute_3 = attribute_3
还有另一个 BaseClass,它期望 BaseClass 的实例按如下方式工作:
class BaseActionClass:
def __init__(self, attribute_a1: BaseClass, attribute_a2: Dict[str, str])
self.attribute_a1 = attribute_a1
self.attribute_a2 = attribute_a2
def do_action_one(self):
pass
def do_action_two(self):
pass
有一个 ActionClass 使用此 BaseActionClass 来执行一些操作:
class ActionClass(BaseActionClass):
def __init__(self, attribute_a1: DataClass, attribute_a2: Dict[str, str]):
super().__init__(attribute_a1, attribute_a2)
def do_action_one(self):
do_statement_1
x = self.attribute_a1.attribute_1
y = self.attribute_a1.attribute_2
def do_action_two(self):
do_something
在 ActionClass.do_action_one 中,当写入 y = self.attribute_a1.attribute_2 时,PyCharm 显示
Unresolved attribute reference 'attribute_2' for class 'BaseClass'
的打字错误。如何解决 IDE 显示的此键入错误?既然 DataClass 已经继承自 BaseClass,为什么会发生这种情况?
现在的问题是
ActionClass.attribute_a1
仍然是BaseClass
,因为它的基数是这样声明的。这绝对没问题,因为在 ActionClass
中,你不会强制 attribute_a1
成为 DataClass
,而只限制 __init__
方法。如果是另一种方法(不是 __init__
,而是 set_attribute_a1
- 让我们暂时忘记属性),您也会以这种方式违反 LSP。
我可以建议两种方法:
我假设
BaseClass
和 DataClass
的定义是你的。然后以下将起作用:
from typing import Generic, TypeVar
_T = TypeVar('_T', bound=BaseClass)
class BaseActionClass(Generic[_T]):
def __init__(self, attribute_a1: _T, attribute_a2: dict[str, str]) -> None:
self.attribute_a1: _T = attribute_a1
self.attribute_a2 = attribute_a2
def do_action_one(self) -> None:
pass
class ActionClass(BaseActionClass[DataClass]):
# Note you don't even need to override __init__ now, it follows from generic defn
def do_action_one(self) -> None:
self.attribute_a1.attribute_1
self.attribute_a1.attribute_2
class BaseActionClass:
def __init__(self, attribute_a1: BaseClass, attribute_a2: dict[str, str])
self.attribute_a1 = attribute_a1
self.attribute_a2 = attribute_a2
def do_action_one(self):
pass
def do_action_two(self):
pass
class ActionClass(BaseActionClass):
attribute_1: DataClass
def __init__(self, attribute_a1: DataClass, attribute_a2: dict[str, str]):
super().__init__(attribute_a1, attribute_a2)
def do_action_one(self):
self.attribute_a1.attribute_1
self.attribute_a1.attribute_2
前一种解决方案是首选,因为它首先揭示了您的意图并且在语义上更正确。大致意思如下:类
BaseActionClass
具有 attribute_a1
类型的 _T
参数,该参数可以被任何 BaseClass
子类(包括 BaseClass
本身)替换。当您对 BaseActionClass[DataClass]
进行子类化时,您会强制将 _T
替换为 DataClass
。您仍然可以执行 BaseActionClass(BaseClass(), {})
,并且 _T
将是 BaseClass
,但 ActionClass(BaseClass(), {})
现在被拒绝。
后一种解决方案不太优雅。我建议仅当您无权修改时才使用它
BaseActionClass
(例如,它是第 3 方模块,并且您不想/无法为其创建 PR)。