如何让子类自动调用父类的__init__方法?

7
我有一个名为CacheObject的类,它有许多子类。
现在我需要在所有继承自此类的子类中添加一些通用内容,所以我写了以下代码:
class CacheObject(object):

    def __init__(self):
        self.updatedict = dict() 

但是子类没有获得更新的updatedict属性。我知道在Python中调用super init函数是可选的,但是否有一种简单的方法来强制所有子类添加init而不是逐个遍历所有类并修改它们?


你是否正在寻找一个能够正确处理多重继承的解决方案?如果是这样,那么在任何情况下都必须调用 super。如果子类没有定义自己的 __init__,则会自动调用基类的 __init__。否则,它们必须包含对 super().__init__ 的调用。 - shx2
3
如果您希望基类能够运行初始化代码,那么在Python中调用超类的init函数是必需的,尽管这是可选的。 - Steve Jessop
如果我反转控制,子类仍然需要被修改。所有子类的__init__都必须重命名。 - user2003548
1
你需要在继承自CacheObject的类中添加super行的数量是多少?虽然我喜欢这个问题背后的概念,但它似乎有点不可靠。而且你只需要添加一次super(到每个类中)。对我来说,这似乎并不特别繁琐。而且你知道他们说什么-显式优于隐式。 - Wayne Werner
6个回答

10
我曾遇到这样一种情况,希望每个类在调用自己的构造函数之前都能首先调用其基类的构造函数。以下是Python3代码,可以实现上述要求:
  class meta(type):
    def __init__(cls,name,bases,dct):
      def auto__call__init__(self, *a, **kw):
        for base in cls.__bases__:
          base.__init__(self, *a, **kw)
        cls.__init__child_(self, *a, **kw)
      cls.__init__child_ = cls.__init__
      cls.__init__ = auto__call__init__

  class A(metaclass=meta):
    def __init__(self):
      print("Parent")

  class B(A):
    def __init__(self):
      print("Child")

为了说明,它会表现如下:
>>> B()
Parent
Child
<__main__.B object at 0x000001F8EF251F28>
>>> A()
Parent
<__main__.A object at 0x000001F8EF2BB2B0>

谢谢。我在使用这个时有一个问题。你能看一下吗?https://stackoverflow.com/q/64753383/6699433 - klutt
嘿@klutt,我查看了那里的答案:两个都非常好和准确。我的代码确实有点混乱,可能需要进行一些调整,以防止构造函数被反复调用。 - james
欢迎回来。我不确定你还在不在。但是你应该编辑这个答案,让其他人知道它并不像看起来那么好。 - klutt

6
我建议一个非代码修复:
在子类使用其定义的任何其他方法之前,记录super().__init__()应该被调用。
这并不是一种罕见的限制。例如,查看标准库中threading.Thread的文档,其中说:

如果子类重写了构造函数,则必须确保在对线程执行任何其他操作之前调用基类构造函数(Thread.__init__())。

可能有许多其他示例,我只是碰巧打开了那个文档页面。

4

您可以重写__new__方法。只要您的基类没有重写__new__方法而没有调用super().__new__,那么您就没问题了。

class CacheObject(object):
    def __new__(cls, *args, **kwargs):
        instance = super().__new__(cls, *args, **kwargs)
        instance.updatedict = {}
        return instance

class Foo(CacheObject):
    def __init__(self):
        pass

然而,正如一些评论者所说,这样做的动机似乎有点可疑。也许你只需要添加超级调用即可。

1
这是本帖中最干净的答案。 - joeyagreco
CacheObject.__new__Foo.__init__ 的计算之前还是之后被调用? - Eduardo Pignatelli
__new__ 创建一个对象,然后调用 __init__ 来初始化刚创建的对象。 - Lie Ryan

3

这不是您要求的翻译,但是可以尝试将 updatedict 设为属性,这样就不需要在 __init__ 中设置了:

class CacheObject(object):
    @property
    def updatedict(self):
        try:
            return self._updatedict
        except AttributeError:
            self._updatedict = dict()
            return self._updatedict

希望这能实现真正的目标,即你不想每次都去修改每个子类(当然,除了确保没有其他属性叫做“updatedict”)。但是有一些奇怪的问题,因为它与在您的问题中在__init__中设置“updatedict”不同。例如,CacheObject().__dict__的内容不同。它没有键“updatedict”,因为我把那个键放在类中,而不是每个实例中。

1
如果您使用了cached_property,您可以返回空字典。一旦它被访问,它将设置缓存,然后像常规属性一样运行。 - Lie Ryan

0
无论动机如何,另一个选择是使用__init_subclass__()(Python 3.6+)来实现这种行为。(例如,我正在使用它是因为我希望不熟悉Python细节的用户能够从一个类继承以创建特定的工程模型,并且我试图保持他们需要定义的类的结构非常基本。)
对于你的例子来说,
class CacheObject:
    def __init__(self) -> None:
        self.updatedict = dict()

    def __init_subclass__(cls) -> None:        
        orig_init = cls.__init__

        @wraps(orig_init)
        def __init__(self, *args, **kwargs):
            orig_init(self, *args, **kwargs)
            super(self.__class__, self).__init__()
        
        cls.__init__ = __init__

这样做的作用是,任何继承了CacheObject类的子类,在创建时,其__init__函数将被父类包装起来——我们用一个新函数替换它,该函数调用原始函数,然后调用super()(父类)的__init__函数。因此,即使子类覆盖了父类的__init__,在实例创建时,它的__init__也会被一个调用自身和父类的函数包装起来。

-2
你可以为你的类添加装饰器:
def my_decorator(cls):
    old_init = cls.__init__
    def new_init(self):
        self.updatedict = dict()
        old_init(self)
    cls.__init__ = new_init
    return cls

@my_decorator
class SubClass(CacheObject):
    pass

如果您想要自动将装饰器添加到所有子类中,请使用元类:
class myMeta(type):
    def __new__(cls, name, parents, dct):
        return my_decorator(super().__new__(cls, name, parents, dct))

class CacheObject(object, metaclass=myMeta):
    pass

所有的CacheObject实例将共享同一个字典 - 这绝对不是OP想要的。他希望每个实例都有一个实例属性。此外,Python3不再使用__metaclass__,现在是class CacheObject(object, metaclass=myMeta) - mata

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接