【问题标题】:Calling load functions in correct order from constructors从构造函数中以正确的顺序调用加载函数
【发布时间】:2011-10-12 14:20:00
【问题描述】:

我有两个类,如下所示:

class Super(object):
    def __init__(self, arg):
        self.arg = arg

    def load(self):
        # Load data from disk if available
        try:
            self.data = load_routine()
        except IOError as e:
            if e[0] == errno.ENOENT:
                pass
            else:
                raise


class Sub(Super):
    def __init__(self, arg2, *args, **kwargs):
        super(Sub, self).__init__(*args, **kwargs)
        self.arg2 = arg2

    def load(self):
        # Load files specific to superclass
        super(Sub, self).load()
        # Load data from disk if available
        try:
            self.data2 = another_different_load_routine(self.arg2)
        except IOError as e:
            if e[0] == errno.ENOENT:
                pass
            else:
                raise

我希望代码足够清晰,除非我错过了什么,否则这应该适用于两个类,例如:obj = Super(); obj.load()

但是,我实际上希望在 __init__() 的末尾自动调用 load 方法,但我不确定如何实现这一点。如果我在两个类的__init__() 末尾添加self.load(),则子类的load() 方法将被调用两次,如果尝试创建Sub 的实例,一次来自超类,一次来自子类。

如果我只在超类的 init 方法的末尾调用self.load(),它只会被调用一次,但对象还没有被初始化为包含加载所需的属性。

在实例化Sub 时,我想要的是它为超类和子类初始化__init__ 中的所有属性,并为超类和子类调用load()。是否按顺序

Super -> __init__()Super -> load()Sub -> __init__()Sub -> load()

Super -> __init__()Sub -> __init__()Super -> load()Sub -> load()

不重要。我怎样才能做到这一点?

【问题讨论】:

    标签: python oop inheritance constructor


    【解决方案1】:

    答案是你没能列举出来的组合:

    class Super(object):
        def __init__(self):
            print 'init super'
            if self.__class__ == Super:
                self.load()
        def load(self):
            print 'load super'
    
    class Sub(Super):
        def __init__(self):
            # always do super first in init
            super(Sub, self).__init__()
            print 'init sub'
            self.load()
        def load(self):
            # load is essentially an extension of init
            # so you still need to call super first
            super(Sub, self).load()
            print 'load sub'
    
    sub = Sub()
    

    如果你真的想实例化 super(它不是一个抽象类),你需要在它的 init 中进行 if 测试。否则,您无法使用当前的类结构和初始化方案获得所需的有序语义。

    Sub() 将调用Sub.__init__,后者将调用Super.__init__(在执行任何操作之前)。

    之后,__init__ 上的Sub 会这样做。

    最后,Sub.__init__ 将调用Sub.load,后者将调用Super.load(在做任何事情之前),然后做自己的工作。

    执行此操作的“正常”方式是

    sub = Sub()
    sub.load()
    sup = Super()
    sup.load()
    

    根本没有调用load__init__ 方法。如果你真的想在load 中调用级别,这可能是我推荐的,因为它本质上是第二组__init__s。

    编辑:阅读有关前两个版本中失败的 cmets(并查看编辑以查看旧版本)。这是另一个版本,使用元类:

    class Loader(type):
        def __new__(cls, name, bases, attrs):
            if attrs.get('__init__'):
                attrs['_init'] = attrs['__init__']
                del attrs['__init__']
            if attrs.get('_init_'):
                attrs['__init__'] = lambda self: self._init_()
                attrs['_init'] = lambda self: None
            return super(Loader, cls).__new__(cls, name, bases, attrs)
    
    class Super(object):
        __metaclass__ = Loader
        def _init_(self):
            print 'init super'
            self._init()
            self.load()
    
        def load(self):
            print 'load super'
    
    class Sub(Super):
        def __init__(self):
            print 'init sub'
    
        def load(self):
            super(Sub, self).load()
            print 'load sub'
    
    
    sub = Sub()
    sup = Super()
    

    这有一个不同的限制:所有子类都可以正常运行,除了它们不能调用Sub.__init__,通过使用super().__init__()。我认为可以取消此限制,但如果没有另一层间接,我不知道现在如何。

    【讨论】:

    • 如果创建Super() 的实例会怎样?
    • 请注意我的编辑处理 Super() 是一个具体的类。
    • 如果我们有不需要任何自定义初始化的子类,那么您编辑的版本仍然会中断,因此它们根本不会覆盖 __init__
    • 你说得对,我只是改变了约束,我没有删除它。我认为答案是要正确完成,这个调用结构必须从类外部完成,使用您在评论中描述的 classmethod 或两个单独的调用。
    【解决方案2】:

    如果您总是希望 __init__() 完全初始化对象,然后调用 load(),那么我会将这两个函数提取到单独的方法中:

    class Super(object):
        def __init__(self, *args, **kw):
            self._initialise(*args, **kw)
            self.load()
    
        def _initialise(self, arg):
            self.arg = arg
    
        def load(self):
            ... as before ...
    
    
    class Sub(Super):
        def _initialise(self, arg2, *args, **kwargs):
            super(Sub, self)._initialise(*args, **kwargs)
            self.arg2 = arg2
    
        def load(self):
            super(Sub, self).load()
            ... as before ...
    

    现在您有了一个您覆盖的__init__(),并且您不再将初始化和加载这两个单独的操作混合在一起。

    【讨论】:

    • 这很好,可以按预期工作,但是您会失去在__init__ 中初始化的正常行为。如果要使用此方法,最好自定义Super.__new__Super.__metaclass__ 以将Sub.__init__ 重写为Sub._initialize,并创建一个包装器Super.__init__ 以调用self._initialize。这样子类可以用__init__ 正常编写,并且它仍然“正常工作”,就像在我的(有点hackish)实现中一样。
    • 我没有发现问题;您已经为您希望 __init__ 的工作方式添加了约束,因此任何子类都必须遵循这些约束。您也可以通过在类方法中进行构造来执行您建议的操作:这样您就不必重写 __new__ 并且创建实例的用户可以通过调用 @ 之类的东西来选择是否自动调用 load() 987654336@
    猜你喜欢
    • 2014-01-28
    • 2013-06-24
    • 1970-01-01
    • 1970-01-01
    • 2011-11-24
    • 1970-01-01
    相关资源
    最近更新 更多