初始化从基类型派生的类



我正在创建一个名为Environment的类,该类对字典进行子类。它看起来像这样:

class Env(dict):
"An environment dict, containing the parent Env (or None) where created."
def __init__(self, parent=None):
self.parent = parent
# super().__init__() <-- not included

Pylint抱怨说:

super-init-not-called:不调用基类 'dict' 中的__init__方法。

dict类型执行super()有什么作用?这是需要做的事情吗?如果是,为什么有必要?

在玩了一下之后,我不太确定它做了什么(或者无论如何它会自动在幕后做super)。下面是一个示例:

class Env1(dict):
def __init__(self, parent=None):
self.parent = parent
super().__init__()
class Env2(dict):
def __init__(self, parent=None):
self.parent = parent
dir(Env1()) == dir(Env2()), len(dir(Env1))
(True, 48)

Pylint 不知道dict.__init__是做什么的。它无法确定该方法中是否存在一些重要的设置逻辑。这就是为什么它会警告你,这样你就可以决定打电话给super().__init__以确保安全,或者如果你确信你不需要这个电话,就把警告静音。

我很确定当您要将实例初始化为空字典时,您无需调用dict.__init__。但这可能取决于您从中继承的dict类的实现细节(它在 C-API 等效__new__中完成所有设置)。另一个 Python 实现可能会在__init__中为其字典完成更多的设置工作,然后您的代码将无法正常工作。

为了安全起见,通常最好调用父类的__init__方法。这是一个如此广泛的建议,以至于它被烘烤到皮林特中。您可以忽略这些警告,甚至可以在代码中添加注释,以抑制不适用于代码某些部分的警告(这样它们就不会分散您对实际问题的注意力)。但大多数警告通常都很好遵守,即使它们不反映当前代码中的严重错误。

调用super()不是必需的,但如果你想遵循 OOP,特别是 Liskov 替换原则,这是有意义的。

来自维基百科,利斯科夫替换原理说:

如果 S 是 T 的子类型,则 T 类型的对象可以替换为 S 类型的对象,而不会改变程序的任何所需属性。

简单来说,让ST的一个子类。如果T具有方法或属性,则S也有方法或属性。此外,如果T.some_method(arg1, arg2,...,argn)是适当的语法,那么S.some_method(arg1, arg2, ..., argn)也是正确的语法,并且输出是相同的。(还有更多内容,但为了简单起见,我跳过了它)

这个理论对我们的情况意味着什么?如果dict在初始化期间声明了任何属性(parent除外),它们将丢失,并且违反了 Liskov 替换原则。请检查以下示例。

class T:
def __init__(self):
self.t = 1
class S(T):
def __init__(self, parent=None):
self.parent = parent
s = S()
s.t

引发此错误,因为类S无权访问属性t

为什么在我们的案例中没有错误?因为在父类dict中没有在__init__中创建任何属性。因此,该扩展运行良好,不违反 OOP。

要修复 PyLint 问题,请按如下所示更改代码:

class Env(dict):
def __init__(self, parent=None):
super().__init__() # get all parent's __init__ setup
self.parent = parent # add your attributes

它所做的就是文档教给我们的:它调用父类的__init__方法。 这将执行您希望从父级继承的属性背后的所有初始化。

通常,如果不call super().__init__(),则对象只有添加的parent字段,以及对父级的方法和属性的访问权限。 这对于任何不使用初始化参数的类(警告除外)都很好用,或者,特别是一个不动态初始化任何字段的类。

Python 内置类型可以做你期望(或想要)的事情,所以你给定的用法是可以的。


相反,请考虑将 Env 类扩展到名为 Context 的类的情况:

class Context(Env):
def __init__(upper, lower):
self.upper = upper
self.lower = lower
ctx = Context(7, 0)
print(ctx.upper)
print(ctx.parent)

在最后一条语句中,你会得到一个运行时错误:ctx没有属性parent,因为我从来没有在Context.__init__中调用过super().__init__()

最新更新