【问题标题】:Calling base class method after child class __init__ from base class __init__?从基类 __init__ 调用子类 __init__ 之后的基类方法?
【发布时间】:2017-12-13 12:47:18
【问题描述】:

这是我在几种语言中都错过的一个功能,想知道是否有人知道如何在 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__-functions(所有派生类的)都运行之后运行,那太好了。所以调用顺序是这样的:

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

然后对象构造就完成了。这也有点类似于使用setuprunteardown 函数进行单元测试。

【问题讨论】:

  • 你想在子类中重写这个方法吗?
  • 不,我想要在调用所有子级的 init 函数之后调用另一个函数。
  • 你可以用元类来做到这一点,虽然更复杂:\
  • 如果你把self.my_rebind_function()放在基类中 __init__(),当它被派生类的调用时,它会调用派生类版本的my_rebind_function() __init__().
  • @martineau:是的,当然,但这不是我想要的。我希望它在派生类__init__-method 之后调用

标签: python python-3.x class constructor


【解决方案1】:

你可以用这样的元类来做到这一点:

    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/

【讨论】:

    【解决方案2】:

    如果您希望在实例化从Base 继承的类型的每个实例之后调用“重新绑定”函数,那么我会说这个“重新绑定”函数可以存在于Base 类(或任何类)之外继承自它)。

    您可以拥有一个工厂函数,在调用它时为您提供所需的对象(例如give_me_a_processed_child_object())。这个工厂函数基本上是实例化一个对象并在它返回给你之前对它做一些事情。

    将逻辑放入__init__ 不是一个好主意,因为它会掩盖逻辑和意图。当您编写kid = Child() 时,您不会期望在后台发生很多事情,尤其是作用于您刚刚创建的Child 实例的事情。您期望的是Child 的新实例。

    然而,工厂函数透明地对对象执行某些操作并将其返回给您。这样你就知道你得到了一个已经处理过的实例。

    最后,您希望避免将“重新绑定”方法添加到您的 Child 类中,因为所有这些逻辑都可以放在您的工厂函数中。

    【讨论】:

      【解决方案3】:

      我可能还没有完全理解,但这似乎符合我(认为)你想要的:

      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
      

      【讨论】:

      • 这解决了问题,非常好,但是这个解决方案有一个问题。现在我必须拥有other_stuff-方法,它实际上是Child-class 中__init_-方法的替代品。因此,现在Child-class 的用户必须将代码从其正常位置 (__init__) 移动到一些新方法 (other_stuff)..
      • Child 类的用户 不会编写任何代码。它的实现者只需将所需的代码放在不同的位置(方法)。对我来说听起来没什么大不了的......
      猜你喜欢
      • 2013-10-12
      • 2017-08-26
      • 1970-01-01
      • 1970-01-01
      • 2013-12-19
      • 1970-01-01
      • 2016-10-05
      • 1970-01-01
      • 2011-01-05
      相关资源
      最近更新 更多