开发者

Python: Correct way to initialize when superclasses accept different arguments?

If I've got three classes like this:

class BaseClass(object):
    def __init__(self, base_arg, base_arg2=None):
        ...

class MixinClass(object):
    def __init__(self, mixin_arg):
        ..开发者_如何转开发.

class ChildClass(BaseClass, MixinClass):
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        ???

What is the correct way to initialize MixinClass and BaseClass?

It doesn't look like I can use super because the MixinClass and the BaseClass both accept different arguments… And two calls, MixinClass.__init__(...) and BaseClass.__init__(...), will could cause the diamond inheritence problem super is designed to prevent.


Basically, in Python, you can't support this type of inheritance safely. Luckily you almost never need to, since most methods don't care what something is, only that it supports a particular interface. Your best bet is to use composition or aggregation: have your class inherit from one of the parent classes, and contain a reference to an instance of the second class. As long as your class supports the interface of the second class (and forwards messages to the contained instance) this will probably work out fine.

In the above example (where both classes inherit from object) you can (probably) safely just inherit from both classes and call both constructors using MixinClass.__init__ and BaseClass.__init__. But note that's not safe to do if the parent classes call super in their constructors. A good rule of thumb is to use super if the parent classes use super, and __init__ if the parent classes use __init__, and hope you're never trapped having to inherit from two classes which chose different methods for initialization.


Just to extend on the correct answer from @chris-b, here follow some examples based on the OP use case and a pattern I have already tried (and mostly failed, at least according in terms of code beauty).

recap

As a summary, if you call super.__init__ in each and every class init you write, python will nicely follow the MRO to call all the inits for all the classes when multiple inheritance is used. The super.__init__ works calling the parent.__init__, and delegating the parent.__init__ to call all it's siblings inits.

It follows that, for a simple class C(A, B), B.__init__ will only be called if A.__init__ itself calls super.__init__, even if C.__init__ uses super.__init__.

The alternative is to manually call the inits that you want, eg. A.__init__(self) and B.__init__(self) in C.__init__; the disadvantage is that this pattern potentially breaks future inherited classes that call super and expect all the parent inits to be called as well. One must /know/ what the various parent inits do.

One would thus think that using super.__init__ all the time is the correct thing to do; but as the OP states, this 'magic' chain of calls breaks when different arguments are expected by the different init (a common thing with a mixin pattern!).

More info can be found in How does Python's super() work with multiple inheritance?

Is there a perfect solution?

Unfortunately. it seems like in Python use of multiple inheritance (and mixin patterns) requires some knowledge of what is going on at multiple levels;

even trying to plan for extended cases by accepting *args and **kwargs and calling super.__init__ passing all arguments will fail because object.init() accept only one parameter (self)!

That is shown in the first example hereafter.

An extremely ugly hack I've been using and that works (albeit potentially not for all possible situations) is to wrap the calls to super.init in try except blocks such as:

try:
    super(ThisClass, self).__init__(*args, arg1=arg1, arg2=arg2, **kwargs)
except TypeError as e:
    # let's hope this TypeError is due to the arguments for object...
    super(ThisClass, self).__init__()

That seems to work - but really ugly.

I've made a gist: https://gist.github.com/stefanocrosta/1d113a6a0c79f853c30a64afc4e8ba0a

but just in case these are the examples:

Full Example 1

class BaseClass(object):
    def __init__(self, base_arg, base_arg2=None, *args, **kwargs):
        print "\tBaseClass: {}, {}".format(base_arg, base_arg2)
        super(BaseClass, self).__init__(*args, base_arg=base_arg, base_arg2=base_arg2, **kwargs)

class MixinClass(object):
    def __init__(self, mixin_arg, *args, **kwargs):
        print "\tMixinClass: {}".format(mixin_arg)
        super(MixinClass, self).__init__()

class MixinClassB(object):
    def __init__(self, mixin_arg, *args, **kwargs):
        print "\tMixinClassB: {}".format(mixin_arg)
        super(MixinClassB, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)

class ChildClassA(BaseClass, MixinClass):
    """
    Let's make it work for this case
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassA, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)

class ChildClassB(BaseClass, MixinClass):
    """
    Same as above, but without specifying the super.__init__ arguments names
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        # If you don't specify the name of the arguments, you need to use the correct order of course:
        super(ChildClassB, self).__init__(base_arg, base_arg2, mixin_arg)

class ChildClassC(BaseClass, MixinClassB, MixinClass):
    """
    Now let's simply add another mixin: before...
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassC, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)

class ChildClassD(BaseClass, MixinClass, MixinClassB):
    """
    Now let's simply add another mixin: ..and after
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassD, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)        

childA = ChildClassA(1, 3, 2)  # note the order of the arguments - the mixin arg is interleaved
childB = ChildClassB(1, 3, 2)
childC = ChildClassC(1, 3, 2)
childD = ChildClassD(1, 3, 2)

Full Example 2:

class BaseClass(object):
    def __init__(self, base_arg, base_arg2=None, *args, **kwargs):
        print "\tBaseClass: {}, {}".format(base_arg, base_arg2)
        try:
            super(BaseClass, self).__init__(*args, base_arg=base_arg, base_arg2=base_arg2, **kwargs)
        except:
            super(BaseClass, self).__init__()

class MixinClass(object):
    def __init__(self, mixin_arg, *args, **kwargs):
        print "\tMixinClass: {}".format(mixin_arg)
        try:
            super(MixinClass, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)
        except:
            super(MixinClass, self).__init__()

class MixinClassB(object):
    def __init__(self, mixin_arg, *args, **kwargs):
        print "\tMixinClassB: {}".format(mixin_arg)
        try:
            super(MixinClassB, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)
        except:
            super(MixinClassB, self).__init__()

class ChildClassA(BaseClass, MixinClass):
    """
    Let's make it work for this case
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassA, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)        


class ChildClassC(BaseClass, MixinClassB, MixinClass):
    """
    Now let's simply add another mixin: before...
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassC, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)

class ChildClassD(BaseClass, MixinClass, MixinClassB):
    """
    Now let's simply add another mixin: ..and after
    """
    def __init__(self, base_arg, mixin_arg, base_arg2=None):
        print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
            self.__class__.__name__, base_arg, mixin_arg, base_arg2)
        super(ChildClassD, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)        

try:
    base = BaseClass(1, 2)
except Exception as e:
    print "Failed because object.__init__ does not expect any argument ({})".format(e)
childA = ChildClassA(1, 3, 2)  # note the order of the arguments - the mixin arg is interleaved
childC = ChildClassC(1, 3, 2)
childD = ChildClassD(1, 3, 2)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜