python-3.x 如何使子类自动调用父类__init__?

zbdgwd5y  于 2023-02-20  发布在  Python
关注(0)|答案(6)|浏览(146)

我有一个名为CacheObject类,许多类都是从它扩展而来的。
现在我需要在这个类的所有类上添加一些公共的东西,所以我编写了以下代码

class CacheObject(object):

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

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

pb3s4cty

pb3s4cty1#

我曾经想让类在调用自己的构造函数之前总是按顺序调用基类的构造函数。下面是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>
b5buobof

b5buobof2#

我建议一个非代码修复:
记录super().__init__()应该在子类使用其中定义的任何其他方法之前由子类调用。
这并不是一个不常见的限制,例如,参见标准库中threading.Thread的文档,其中写道:
如果子类重写构造函数,则必须确保在对线程执行任何其他操作之前调用基类构造函数(Thread.__init__())。
可能还有很多其他的例子,我只是碰巧有那个文档页面打开。

rsl1atfo

rsl1atfo3#

你可以重写__new__,只要你的基类在不调用super().__new__的情况下不重写__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

然而,正如一些评论者所说,这样做的动机似乎有点见不得人。你也许应该只添加超级调用来代替。

yzckvree

yzckvree4#

这不是您所要求的,但是将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,因为我把那个键放在了类中,而不是每个示例中。

xfyts7mz

xfyts7mz5#

不管动机如何,另一个选择是使用__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__函数 Package 在父类中-我们用一个新函数替换它,该函数调用原始函数,然后调用super()(父类的)__init__函数。所以现在,即使子类覆盖父类__init__,在示例创建时,其__init__然后由调用它然后调用其父的函数 Package 。

m1m5dgzv

m1m5dgzv6#

你可以给你的类添加一个装饰器:

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

相关问题