【问题标题】:python: calling super().__init__ too early in the __init__ method?python:在 __init__ 方法中过早调用 super().__init__?
【发布时间】:2011-04-26 05:46:21
【问题描述】:

我有一个类层次结构,其中class Base 中的__init__ 执行一些预初始化,然后调用方法calculatecalculate 方法在class Base 中定义,但预计会在派生类中重新定义。重新定义的calculate会用到一些只有class Derived才有的属性:

class Base:
    def __init__(self, args):
        # perform some pre-initialization
        ...
        # now call method "calculate"
        self.calculate()

class Derived(Base):
    def __init__(self, args, additional_attr):
        super().__init__(args)
        # do some work and create new instance attributes
        ...
        self.additional_attr = additional_attr

这是行不通的,因为 class Derived 中的 calculate 方法将在 self.additional_attr 被分配之前被调用。

我无法将super().__init__(args) 调用移到__init__ 方法的末尾,因为它所做的一些工作必须在处理additional_attr 之前发生。

怎么办?

【问题讨论】:

  • 有很多疯狂的方法可以解决这个问题,但没有理智的方法。
  • 也许你不应该在你的构造函数中调用calculate()。如果您不能通过允许基本构造函数首先完成来构造派生对象,那么您一定是做错了恕我直言。
  • 我认为拥有一个依赖于基类中不存在的子类属性的专门的 calculate() 方法是 OO 滥用。通过构建的交叉依赖关系。反对将功能从Base.calculate() 拆分为Derived.additional_calculate() 的有力论据是什么?我们可以给Base.additional_calculate() 一个空的身体。 (或者至少,改进对尚不存在的附加数据的处理,使其更加优雅?)

标签: python class design-patterns


【解决方案1】:

也许你不应该在你的构造函数中调用calculate()。如果您不能通过允许基本构造函数首先完成来构造派生对象,那么您一定做错了恕我直言。一种明智的方法是将调用移出构造函数,并可能创建一个工厂方法来自动进行调用。如果您需要预先计算的实例,请使用该方法。

class Base(object):
    def __init__(self, args):
        # perform some initialization
        pass
    def calculate(self):
        # do stuff
        pass
    @classmethod
    def precalculated(cls, args):
        # construct first
        newBase = cls(args)
        # now call method "calculate"
        newBase.calculate()
        return newBase

class Derived(Base):
    def __init__(self, args, additional_attr):
        super(Derived, self).__init__(args)
        # do some work and create new instance attributes
        self.additional_attr = additional_attr
    @classmethod
    def precalculated(cls, args, additional_attr): # also if you want
        newDerived = cls(args, additional_attr)
        newDerived.calculate()
        return newDerived

newBase = Base('foo')
precalculatedBase = Base.precalculated('foo')
newDerived = Derived('foo', 'bar')
precalculatedDerived = Derived.precalculated('foo', 'bar')

【讨论】:

    【解决方案2】:

    这是糟糕的设计,恕我直言,而且您正在混淆 Python 的对象系统。考虑到在 C++ 等其他 OO 语言中,您甚至无法控制基类的创建。派生类的构造函数在代码运行之前调用基构造函数。行为良好的类层次结构几乎总是会出现这种行为,而改变它只会导致问题。

    当然,您可以进行一些修补(例如在调用 super 的构造函数之前分配 self.additional_attr 或其他技巧),但更好的方法是更改​​您的设计,使其不需要这样的黑客。由于您在这里提供了一个抽象示例,因此很难提供更全面的设计建议。

    【讨论】:

      【解决方案3】:

      为了使这样的事情起作用,您需要设计一个协议,允许基类和派生类相互协作以完成对象初始化任务:

      class Base:
          def __init__(self, args, *additional_args):
              # perform some pre-initialization
              # ...
      
              # perform any futher initialization needed by derived classes
              self.subclass_setup(*additional_args)
      
              # now call method "calculate"
              self.calculate()
      
          def subclass_setup(self, *args):
              pass
      
      class Derived(Base):
          def __init__(self, args, additional_attr):
              super().__init__(args, additional_attr)
      
          def subclass_setup(self, additional_attr):
              # do some work and create new instance attributes
              # ...
              self.additional_attr = additional_attr
      

      【讨论】:

        【解决方案4】:

        您能否将additional_attr 作为参数传递给基类的__init__ 方法并从那里传播到calculate 方法?

        这样说:

        class Base(object): 
            def __init__(self, args,additional_attr): 
                print 'Args for base class:%s' %(args)
                self.calculate(additional_attr)
        
        class Derived(Base):
            def __init__(self, args, additional_attr):
                super(Derived,self).__init__(args,additional_attr)
        
             def calculate(self,val):
                 print 'Arg for calculate:%s' %(val)
                 self.additional_attr = val
        
        >>> d = Derived(['test','name'],100) 基类的参数:['test', 'name'] 计算参数:100

        这是一种迂回的方式,但没有关于预初始化步骤的信息,很难说上述方法是否会对您有所帮助。

        【讨论】:

        • 帮助格式化,只需将代码块缩进 4 个空格,它应该为您正确格式化。无需使用<code> 块。 ;)
        猜你喜欢
        • 2015-12-09
        • 2017-06-30
        • 2020-08-14
        • 2015-12-06
        • 2020-07-06
        • 2020-09-20
        • 2011-11-06
        • 2020-05-17
        • 2022-11-25
        相关资源
        最近更新 更多