首页 > 解决方案 > 超类 __init__() 中的子类特定属性

问题描述

我环顾四周,但找不到任何答案。我有一个小问题——我有一个带有一些抽象方法的抽象基类,但也有几个对所有子类通用的方法。然而,为了使用这些方法,我需要传递一个特定于子类的属性。这很好用,但是我当然会收到基类没有特定属性的警告:

Unresolved attribute reference 'c' for class 'Foo'

假设我有这个代码:

from abc import ABC

class Foo(ABC):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def do_stuff(self):
        if hasattr(self, 'c'):
            return self.a * self.c
        elif hasattr(self, 'd'):
            return self.a + self.d


class Bar(Foo):
    def __init__(self, a, b, c):
        super().__init__(a=a, b=b)
        self.a = a
        self.b = b
        self.c = c
        
        self.some_dict = {}

    def get_value_from_dict(self):
        return self.some_dict[self.d]

class Baz(Foo):
    def __init__(self, a, b, d):
        super().__init__(a=a, b=b)
        self.a = a
        self.b = b
        self.d = d

所以,Foo是一个抽象基类,所以它永远不会被自己调用,但当然有这些警告并不好。然而,如果我将属性添加c到值为 的基类None,则会导致错误,因为当子类调用超类的 init 时,该值会被覆盖:

class Foo(ABC):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.c = None

如果我如上所示更改基类的 init 然后实例化类Bar并调用get_value_from_dict()我将得到一个KeyError,否则如果我保持原始示例中的内容,那么一切正常:

b = Bar(1, 2, 3)
b.do_stuff()
b.get_value_from_dict()

编辑: 这是我正在使用的实际代码。这就是do_stuff我的示例中的方法所要表示的内容。这self.component是一个特定于子类的属性,此通用方法将错误值替换为占位符值。

基类中还有其他几个self.component以类似方式使用的通用方法。

class VariableImputer(ABC):
    def __init__(self, data: pd.DataFrame, deposit: str, output_loc: Optional[str] = None) -> None:
        self.data = data
        self.deposit = deposit
        self.output_loc = output_loc
        self.err_index: np.ndarray = np.full(self.data.shape[0], True)

    def _replace_erroneous_values(self):
        """
        Replace calculated component values with -99 for all rows indices of
        which are in self.err_index.
        """
        err_data = np.where(~self.err_index)[0]
        self.data.loc[err_data, self.component] = -99

class PopulateValue(VariableImputer):

    def __init__(self, data: pd.DataFrame, deposit: str, output_loc: Optional[str] = None):
        super().__init__(data=data, deposit=deposit, output_loc=output_loc)

        self.data = data
        self.deposit = deposit
        self.output_loc = output_loc

        self.component = ['porosity', 'sg']

但是警告仍然存在。处理这种情况的正确方法是什么?

标签: pythonoopsubclass

解决方案


所以,如果我做这样的事情,我的 linter 就会停止抱怨:

from abc import ABC


class Foo(ABC):
    a: int
    b: int
    c: int
    d: int
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def _do_stuff(self):
        if hasattr(self, 'c'):
            return self.a * self.c
        elif hasattr(self, 'd'):
            return self.a + self.d


class Bar(Foo):
    def __init__(self, a, b, c):
        super().__init__(a=a, b=b)
        self.a = a
        self.b = b
        self.c = c


class Baz(Foo):
    def __init__(self, a, b, d):
        super().__init__(a=a, b=b)
        self.a = a
        self.b = b
        self.d = d

另一种选择,使用哨兵值而不是检查hasattr

从 abc 进口 abc

class Foo(ABC):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def _do_stuff(self):
        if self.c is not None:
            return self.a * self.c
        elif self.d is not None:
            return self.a + self.d

但总的来说,这对我来说似乎是代码味道。您只是在避免基本问题,即您的方法可能不应该在 中实现Foo,而是应该是在子类中实现的抽象方法。

为什么不只是

from abc import ABC, abstractmethod


class Foo(ABC):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    @abstractmethod
    def _do_stuff(self):
        ...

class Bar(Foo):
    def __init__(self, a, b, c):
        super().__init__(a=a, b=b)
        self.a = a
        self.b = b
        self.c = c
    def _do_stuff(self):
        return self.a * self.c


class Baz(Foo):
    def __init__(self, a, b, d):
        super().__init__(a=a, b=b)
        self.a = a
        self.b = b
        self.d = d
    def _do_stuff(self):
        return self.a * self.d

这对我来说似乎是最明智的解决方案。


推荐阅读