从基类__init__调用子类__init__后调用基类方法?

7

这是我在几种编程语言中都想要的一个功能,我想知道是否有人知道如何在Python中实现。

我的想法是,我有一个基类:

class Base(object):
    def __init__(self):
        self.my_data = 0
    def my_rebind_function(self):
        pass

和一个派生类:

class Child(Base):
    def __init__(self):
        super().__init__(self)
        # Do some stuff here
        self.my_rebind_function() # <==== This is the line I want to get rid of
    def my_rebind_function(self):
        # Do stuff with self.my_data

如上所示,我有一个回弹函数,我希望在Child.__init__完成其工作后调用它。我希望对所有继承的类都执行此操作,因此如果基类执行该操作,那将是很好的,这样我就不必在每个子类中重新输入该行。

如果语言有像__finally__这样的函数,它的操作方式类似于异常处理,那将是很好的。也就是说,它应该在所有派生类的__init__函数运行后运行,这将非常棒。因此,调用顺序应该类似于:

Base1.__init__()
...
BaseN.__init__()
LeafChild.__init__()
LeafChild.__finally__()
BaseN.__finally__()
...
Base1.__finally__()

接下来,对象的构建工作已经完成。这有点类似于使用setuprunteardown函数进行单元测试。


你想在子类中覆盖这个方法吗? - PRMoureu
不,我想要在所有子类的__init__函数被调用后再调用另一个函数。 - Robert
你可以使用元类来实现,尽管会更加复杂 :\ - GIZ
如果您将 self.my_rebind_function() 放在基类 __init__() 中,当派生类的 __init__() 调用它时,它将调用派生类版本的 my_rebind_function() - martineau
@martineau:是的,当然可以,但那不是我想要的。我希望它在派生类__init__方法之后被调用。 - Robert
3个回答

8
你可以使用元类来实现这个功能,代码如下:
    class Meta(type):
        def __call__(cls, *args, **kwargs):
            print("start Meta.__call__")
            instance = super().__call__(*args, **kwargs)
            instance.my_rebind_function()
            print("end Meta.__call__\n")
            return instance


    class Base(metaclass=Meta):
        def __init__(self):
            print("Base.__init__()")
            self.my_data = 0

        def my_rebind_function(self):
            pass


    class Child(Base):
        def __init__(self):
            super().__init__()
            print("Child.__init__()")

        def my_rebind_function(self):
            print("Child.my_rebind_function")
            # Do stuff with self.my_data
            self.my_data = 999


    if __name__ == '__main__':
        c = Child()
        print(c.my_data)

通过重写 Metaclass.__call__,您可以在类树中所有 __init__(和 __new__)方法运行后,在实例返回之前进行挂钩。这是调用重新绑定函数的位置。为了理解调用顺序,我添加了一些打印语句。输出将如下所示:

start Meta.__call__
Base.__init__()
Child.__init__()
Child.my_rebind_function
end Meta.__call__

999

如果您想深入了解并探讨细节,我可以推荐以下优秀的文章:https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/。该文章与Python元类相关。

在一些情况下,这似乎无法正常工作,其中元类中也覆盖了__new__方法(在这种情况下,__call__方法似乎永远不会被调用,至少我认为我可以提供一个示例),有没有什么线索或解决方法? - Matt-Mac-Muffin

2
我可能还没有完全理解,但这似乎可以实现你想要的(我认为):
class Base(object):
    def __init__(self):
        print("Base.__init__() called")
        self.my_data = 0
        self.other_stuff()
        self.my_rebind_function()

    def other_stuff(self):
        """ empty """

    def my_rebind_function(self):
        """ empty """

class Child(Base):
    def __init__(self):
        super(Child, self).__init__()

    def other_stuff(self):
        print("In Child.other_stuff() doing other stuff I want done in Child class")

    def my_rebind_function(self):
        print("In Child.my_rebind_function() doing stuff with self.my_data")

child = Child()

输出:

Base.__init__() called
In Child.other_stuff() doing other stuff I want done in Child class
In Child.my_rebind_function() doing stuff with self.my_data

1
这解决了问题,而且相当不错,但是这个解决方案有一个问题。现在我必须要有 other_stuff 方法,它实际上是 Child 类中 __init__ 方法的替代品。因此,现在 Child 类的用户必须将代码从其正常位置(__init__)移动到一些新方法(other_stuff)中。 - Robert
Child类的用户不需要编写任何代码。实现者只需将所需的代码放在另一个地方(方法)即可。对我来说听起来并不是什么大问题... - martineau

0
如果你想在每个继承自Base的类型实例化后调用一个“重新绑定”函数,那么我会说这个“重新绑定”函数可以存在于Base类(或任何继承自它的类)之外。
你可以拥有一个工厂函数,在调用它时给你需要的对象(例如give_me_a_processed_child_object())。这个工厂函数基本上实例化一个对象,并在返回给你之前对其进行一些操作。
将逻辑放在__init__中不是一个好主意,因为它会掩盖逻辑和意图。当你写kid = Child()时,你不希望在后台发生很多事情,特别是那些作用于你刚刚创建的Child实例的事情。你期望得到的是一个新的Child实例。
然而,一个工厂函数透明地对一个对象进行一些操作并将其返回给你。这样你就知道你得到了一个已经处理过的实例。
最后,你想避免向你的Child类添加“重新绑定”方法,现在你可以做到了,因为所有的逻辑都可以放在你的工厂函数中。

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