简体   繁体   中英

Python inheritance structure and arguments

I am trying to design a class structure that allows the user to define their own class that overloads predefined methods in other classes. In this case the user would create the C class to overload the "function" method in D. The user created C class has common logic for other user created classes A and B so they inherit from C to overload "function" but also inherit from D to use D's other methods. The issue I am having is how to pass "value" from A and B to D and ignore passing it to C. What I currently have written will produce an error as C does not have "value" as an argument.

I know that I can add "value" (or *args) to C's init method and the super call but I don't want to have to know what inputs other classes need in order to add new classes to A and B. Also, if I swap the order of C and DI won't get an error but then I don't use C's overloaded "function". Is there an obvious way around this?

class D(SomethingElse):
    def __init__(self, value, **kwargs):
        super(D, self).__init__(**kwargs)

        self.value = value

    def function(self):
        return self.value

    def other_method(self):
        pass

class C(object):
    def __init__(self):
        super(C, self).__init__()

    def function(self):
        return self.value*2

class B(C, D):
    def __init__(self, value, **kwargs):
        super(B, self).__init__(value, **kwargs)

class A(C, D):
    def __init__(self, value, **kwargs):
        super(A, self).__init__(value, **kwargs)



a = A(3)
print(a.function())
>>> 6

Essentially, there are two things you need to do to make your __init__ methods play nice with multiple inheritance in Python:

  1. Always take a **kwargs parameter, and always call super().__init__(**kwargs) , even if you think you are the base class . Just because your superclass is object doesn't mean you are last (before object ) in the method resolution order .
  2. Don't pass your parent class's __init__ arguments explicitly; only pass them via **kwargs . Your parent class isn't necessarily the next one after you in the method resolution order, so positional arguments might be passed to the wrong other __init__ method.

This is called "co-operative subclassing". Let's try with your example code:

class D:
    def __init__(self, value, **kwargs):
        self.value = value
        super().__init__(**kwargs)
    
    def function(self):
        return self.value

class C:
    # add **kwargs parameter
    def __init__(self, **kwargs):
        # pass kwargs to super().__init__
        super().__init__(**kwargs)
    
    def function(self):
        return self.value * 2

class B(C, D):
    # don't take parent class's value arg explicitly
    def __init__(self, **kwargs):
        # pass value arg via kwargs
        super().__init__(**kwargs)

class A(C, D):
    # don't take parent class's value arg explicitly
    def __init__(self, **kwargs):
        # pass value arg via kwargs
        super().__init__(**kwargs)

Demo:

>>> a = A(value=3)
>>> a.value
3
>>> a.function()
6

Note that value must be passed to the A constructor as a keyword argument, not as a positional argument. It's also recommended to set self.value = value before calling super().__init__ .

I've also simplified class C(object): to class C: , and super(C, self) to just super() since these are equivalent in Python 3.

So I'm trying to understand the point of A AND B. I'm guessing that maybe you want to mix in the superclass behavior and sometimes have local behavior. So suppose A is just mixing together behaviors, and B has some local behavior and state.

If you don't need your own state, you probably don't need an __init__ . So for A and C just omit __init__ .

class SomethingElse(object):
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

class D(SomethingElse):
    def __init__(self, value, *args,  **kwargs):
        super(D, self).__init__(*args, **kwargs)

        self.value = value

    def function(self):
        return self.value

    def other_method(self):
        return self.__dict__

class C(object):
    #def __init__(self):
    #    super(C, self).__init__()

    def function(self):
        return self.value*2

class B(C, D):
    def __init__(self, value, bstate, *args, **kwargs):
        super(B, self).__init__(value, *args, **kwargs)
        self.bstate = bstate

    def __repr__(self):
        return (self.__class__.__name__ + ' ' +
                self.bstate + ' ' + str(self.other_method()))

class A(C, D):
    pass


a = A(3)
b = B(21, 'extra')

a.function()
6

b.function()
42
repr(a)
'<xx.A object at 0x107cf5e10>'
repr(b)
"B extra {'args': (), 'bstate': 'extra', 'value': 21, 'kwargs': {}}"

I've kept python2 syntax assuming you might still be using it, but as another answer points out, python3 simplifies super() syntax, and you really should be using python3 now.

If you swap C and D you are changing the python method resolution order, and that will indeed change the method to which a call to A.function resolves.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM