PyCharm 无法识别继承类的属性

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

有一个基础数据类如下:

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,为什么会发生这种情况?

python inheritance pycharm python-typing
1个回答
0
投票

现在的问题是

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)。

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