Python inheritance structure and arguments - python

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 D I 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:
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.
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.

Related

override multiple attributes inherited from parent class in Python

I'd like to assign different values to inherited attributes in the instances of a child class. The code I use is
class Parent:
def __init__(self, n=50):
# there are multiple parent attributes like 'n'
# I use just one to reproduce the error
self.n = n
def print_n(self):
print('n =', self.n)
class Child(Parent):
def __init__(self, a=5):
self.a = a
def print_a(self):
print('a =', self.a)
son1 = Child(n=100)
son1.print_n()
The error message is
son1 = Child(n=100)
TypeError: __init__() got an unexpected keyword argument 'n'
What would be the correct way to achieve the objective?
I tried to put super().init() in the init method of the child class according to the answer to this similar question, but it didn't work.
Your Child.__init__ needs to call Parent.__init__ explicitly; it won't happen automagically. If you don't want Child.__init__ to have to "know" what Parent.__init__'s args are, use *args or in this case **kwargs to pass through any kwargs that aren't handled by Child.__init__.
class Parent:
def __init__(self, n=50):
# there are multiple parent attributes like 'n'
# I use just one to reproduce the error
self.n = n
def print_n(self):
print('n =', self.n)
class Child(Parent):
def __init__(self, a=5, *args, **kwargs):
super().__init__(*args, **kwargs)
self.a = a
def print_a(self):
print('a =', self.a)
son1 = Child(n=100)
son1.print_n()

Correct way of returning new class object (which could also be extended)

I am trying to find a good way for returning a (new) class object in class method that can be extended as well.
I have a class (classA) which has among other methods, a method that returns a new classA object after some processing
class classA:
def __init__(): ...
def methodX(self, **kwargs):
process data
return classA(new params)
Now, I am extending this class to another classB. I need methodX to do the same, but return classB this time, instead of classA
class classB(classA):
def __init__(self, params):
super().__init__(params)
self.newParams = XYZ
def methodX(self, **kwargs):
???
This may be something trivial but I simply cannot figure it out. In the end I dont want to rewrite the methodX each time the class gets extended.
Thank you for your time.
Use the __class__ attribute like this:
class A:
def __init__(self, **kwargs):
self.kwargs = kwargs
def methodX(self, **kwargs):
#do stuff with kwargs
return self.__class__(**kwargs)
def __repr__(self):
return f'{self.__class__}({self.kwargs})'
class B(A):
pass
a = A(foo='bar')
ax = a.methodX(gee='whiz')
b = B(yee='haw')
bx = b.methodX(cool='beans')
print(a)
print(ax)
print(b)
print(bx)
class classA:
def __init__(self, x):
self.x = x
def createNew(self, y):
t = type(self)
return t(y)
class classB(classA):
def __init__(self, params):
super().__init__(params)
a = classA(1)
newA = a.createNew(2)
b = classB(1)
newB = b.createNew(2)
print(type(newB))
# <class '__main__.classB'>
I want to propose what I think is the cleanest approach, albeit similar to existing answers. The problem feels like a good fit for a class method:
class A:
#classmethod
def method_x(cls, **kwargs):
return cls(<init params>)
Using the #classmethod decorator ensures that the first input (traditionally named cls) will refer to the Class to which the method belongs, rather than the instance.
(usually we call the first method input self and this refers to the instance to which the method belongs)
Because cls refers to A, rather than an instance of A, we can call cls() as we would call A().
However, in a class that inherits from A, cls will instead refer to the child class, as required:
class A:
def __init__(self, x):
self.x = x
#classmethod
def make_new(cls, **kwargs):
y = kwargs["y"]
return cls(y) # returns A(y) here
class B(A):
def __init__(self, x):
super().__init__(x)
self.z = 3 * x
inst = B(1).make_new(y=7)
print(inst.x, inst.z)
And now you can expect that print statement to produce 7 21.
That inst.z exists should confirm for you that the make_new call (which was only defined on A and inherited unaltered by B) has indeed made an instance of B.
However, there's something I must point out. Inheriting the unaltered make_new method only works because the __init__ method on B has the same call signature as the method on A. If this weren't the case then the call to cls might have had to be altered.
This can be circumvented by allowing **kwargs on the __init__ method and passing generic **kwargs into cls() in the parent class:
class A:
def __init__(self, **kwargs):
self.x = kwargs["x"]
#classmethod
def make_new(cls, **kwargs):
return cls(**kwargs)
class B(A):
def __init__(self, x, w):
super().__init__(x=x)
self.w = w
inst = B(1,2).make_new(x="spam", w="spam")
print(inst.x, inst.w)
Here we were able to give B a different (more restrictive!) signature.
This illustrates a general principle, which is that parent classes will typically be more abstract/less specific than their children.
It follows that, if you want two classes that substantially share behaviour but which do quite specific different things, it will be better to create three classes: one rather abstract one that defines the behaviour-in-common, and two children that give you the specific behaviours you want.

When calling super().__init__ how can I tell if the super init is object.__init__ in python3?

Given an arbitrary class inheritance
how do I find out if super().__init__ == object.__init__?
Description + Example
I have this code that I'm not allowed to touch which defines classes A, B, C, CombinedCba, CombinedAc and each class has this weird __init__ constraint which validates instance properties.
When init calls are made in all base classes, we get the error:
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
So to prevent that error, we should stop calling super init when it is object init.
I am able to edit the super_init function. How do I detect when super init is init? If I know that I can not make the next super init call and eliminate the error.
# code that I can edit
def super_init(self, super_instance, *args, **kwargs):
# checking super_instance.__init__ == object.__init__ OR super_instance.__init__ is object.__init__ doesn't work
# pseudo-code
if super_instance.__init__ is not object.__init__:
super_instance.__init__(*args, **kwargs)
# auto generated code is from here on down
class A:
def __init__(self, *args, **kwargs):
self.a = kwargs['a']
assert self.a == 'a'
super_init(self, super(), *args, **kwargs)
class B:
def __init__(self, *args, **kwargs):
self.b = kwargs['b']
self.some_num = kwargs['some_num']
assert self.some_num <= 30
super_init(self, super(), *args, **kwargs)
class C:
def __init__(self, *args, **kwargs):
self.some_num = kwargs['some_num']
assert self.some_num >= 10
super_init(self, super(), *args, **kwargs)
class CombinedCba(C, B, A):
pass
combo_cba = CombinedCba(a='a', b='b', some_num=25)
class CombinedAc(A, C):
pass
combo_ac = CombinedAc(a='a', some_num=15)
The only way that I was able to get this so work was to build a new temporary class
containing the remaining super classes, then checking the __init__ method of that class.
def super_init(self, super_instance, *args, **kwargs):
classes_in_order = self.__class__.__mro__
for i, cls in enumerate(classes_in_order):
if cls == super_instance.__thisclass__:
remainder_cls = type('_unused', classes_in_order[i+1:], {})
super_init_is_object_init = remainder_cls.__init__ == object.__init__
if not super_init_is_object_init:
super_instance.__init__(*args, **kwargs)
These attempts didn't work:
checking super().__init__ == object.__init__ did not work
checking super().__init__ is object.__init__ did not work
checking super(super().__thisclass__, self) vs object.__init__ did not work
introspecting the function signatures with inspect.signature did not work
First, define A, B, and C to use super correctly:
class A:
def __init__(self, a, **kwargs):
super().__init__(**kwargs)
assert a == 'a'
self.a = a
class B:
def __init__(self, b, some_num, *args, **kwargs):
super().__init__(**kwargs)
self.b = b
self.some_num = some_num
assert self.some_num <= 30
class C:
def __init__(self, some_num, **kwargs):
super().__init__(**kwargs)
self.some_num = some_num
assert self.some_num >= 10
In particular, note that both B and C claim "ownership" of some_num, without worrying yet that another class might make use of it.
Next, define a mix-in class that does nothing but ensure that some_num is used to set the some_num attribute.
class SomeNumAdaptor:
def __init__(self, some_num, **kwargs):
self.some_num = some_num
super().__init__(**kwargs)
Third, define wrappers for B and C that get the value of some_num from self in order to add it back as a keyword argument (which SomeNumAdaptor stripped):
class CWrapper(C):
def __init__(self, **kwargs):
super().__init__(some_num=self.some_num, **kwargs)
class BWrapper(B):
def __init__(self, **kwargs):
super().__init__(some_num=self.some_num, **kwargs)
This means that both B and C will "reset" the value of self.num.
(A wrapper isn't necessary if you can also modify B and C to make some_num optional and check for the existence of self.some_num.)
Finally, define your combination classes in terms of SomeNumAdaptor and the wrapper classes. You must inherit from SomeNumAdaptor first, to ensure that BWrapper and CWrapper find some_num as an attribute, regardless of their relative ordering.
class CombinedAbc(SomeNumAdaptor, A, BWrapper, CWrapper):
pass
class CombinedCba(SomeNumAdaptor, CWrapper, BWrapper, A):
pass
combo_cba = CombinedCba(a='a', b='b', some_num=25)
combo_abc = CombinedAbc(a='a', b='b', some_num=15)
The above all assume that neither B nor C store a modified value of its some_num argument to the attribute. If it does, you'll need more complicated wrappers to handle it, likely doing more than simply passing the received value to __init__. Note that this could indicate a more fundamental issue with inheriting from both B and C at the same time.

Change signature of function called in __init__ of base class

In A.__init__ I call self.func(argument):
class A(object):
def __init__(self, argument, key=0):
self.func(argument)
def func(self, argument):
#some code here
I want to change the signature of A.func in B. B.func gets called in B.__init__ through A.__init__:
class B(A):
def __init__(self, argument1, argument2, key=0):
super(B, self).__init__(argument1, key) # calls A.__init__
def func(self, argument1, argument2):
#some code here
Clearly, this doesn't work because the signature of B.func expects two arguments while A.__init__ calls it with one argument. How do I work around this? Or is there something incorrect with the way I have designed my classes?
key is a default argument to A.__init__. argument2 is not intended for key. argument2 is an extra argument that B takes but A does not. B also takes key and has default value for it.
Another constraint is that I would like not to change the signature of A.__init__. key will usually be 0. So I want to allow users to be able to write A(arg) rather than A(arg, key=0).
Generally speaking, changing the signature of a method between subclasses breaks the expectation that the methods on subclasses implement the same API as those on the parent.
However, you could re-tool your A.__init__ to allow for arbitrary extra arguments, passing those on to self.func():
class A(object):
def __init__(self, argument, *extra, **kwargs):
key = kwargs.get('key', 0)
self.func(argument, *extra)
# ...
class B(A):
def __init__(self, argument1, argument2, key=0):
super(B, self).__init__(argument1, argument2, key=key)
# ...
The second argument passed to super(B, self).__init__() is then captured in the extra tuple, and applied to self.func() in addition to argument.
In Python 2, to make it possible to use extra however, you need to switch to using **kwargs, otherwise key is always going to capture the second positional argument. Make sure to pass on key from B with key=key.
In Python 3, you are not bound by this restriction; put *args before key=0 and only ever use key as a keyword argument in calls:
class A(object):
def __init__(self, argument, *extra, key=0):
self.func(argument, *extra)
I'd give func() an *extra parameter too, so that it's interface essentially is going to remain unchanged between A and B; it just ignores anything beyond the first parameter passed in for A, and beyond the first two for B:
class A(object):
# ...
def func(self, argument, *extra):
# ...
class B(A):
# ...
def func(self, argument1, argument2, *extra):
# ...
Python 2 demo:
>>> class A(object):
... def __init__(self, argument, *extra, **kwargs):
... key = kwargs.get('key', 0)
... self.func(argument, *extra)
... def func(self, argument, *extra):
... print('func({!r}, *{!r}) called'.format(argument, extra))
...
>>> class B(A):
... def __init__(self, argument1, argument2, key=0):
... super(B, self).__init__(argument1, argument2, key=key)
... def func(self, argument1, argument2, *extra):
... print('func({!r}, {!r}, *{!r}) called'.format(argument1, argument2, extra))
...
>>> A('foo')
func('foo', *()) called
<__main__.A object at 0x105f602d0>
>>> B('foo', 'bar')
func('foo', 'bar', *()) called
<__main__.B object at 0x105f4fa50>
It seems to be that there is a problem in your design. The following might fix your particular case but seems to perpetuate bad design even further. Notice the Parent.method being called directly.
>>> class Parent:
def __init__(self, a, b=None):
Parent.method(self, a)
self.b = b
def method(self, a):
self.location = id(a)
>>> class Child(Parent):
def __init__(self, a):
super().__init__(a, object())
def method(self, a, b):
self.location = id(a), id(b)
>>> test = Child(object())
Please consider adding a default argument to the second parameter of the method you are overriding. Otherwise, design your class and call structure differently. Reorganization might eliminate the problem.
actually I would resort to put an extra boolean argument in A's __init__ to control the call of the func, and just pass False from B's __init__
class A(object):
def __init__(self, argument, key=0, call_func=True):
if call_func:
self.func(argument)
class B(A):
def __init__(self, argument):
argument1, argument2 = argument, 'something else'
super(B, self).__init__(argument1, argument2, call_func=False)

Access to __init__ arguments

Is is possible to access the arguments which were passed to __init__, without explicitly having to store them?
e.g.
class thing(object):
def __init__(self, name, data):
pass # do something useful here
t = thing('test', [1,2,3,])
print t.__args__ # doesn't exist
>> ('test', [1,2,3])
The use-case for this is creating a super-class which can automatically store the arguments used to create an instance of a class derived from it, without having to pass all the arguments explicitly to the super's __init__. Maybe there's an easier way to do it!
No, you have to store them. Otherwise they are gone after __init__() returns, as all local variables.
If you don't want to pass all arguments on explicitly, you can use **kwargs:
class Base(object):
def __init__(self, name, data):
# store name and data
class Derived(Base):
def __init__(self, **kwargs):
Base.__init__(self, **kwargs)
Derived(name="Peter", data=42)
This is not entirely recommended, but here is a wrapper that automatically stores parameter variables:
from functools import wraps
def init_wrapper(f):
#wraps(f)
def wrapper(self, *args, **kwargs):
func_parameters = f.func_code.co_varnames[1:f.func_code.co_argcount]
#deal with default args
diff = len(func_parameters) - len(args)
if diff > 0:
args += f.func_defaults[-diff:]
#set instance variables
for pos, arg in enumerate(func_parameters):
print pos, arg
setattr(self, arg, args[pos])
f(self, *args, **kwargs) #not necessary to use return on __init__()
return wrapper
Usage:
class A(object):
#init_wrapper
def __init__(self, a, b, c):
print a + b + c
Example:
>>> a = A(1, 2, 3)
6
>>> a.a
1
>>> a.b
2
>>> a.c
3
In a word: No.
What you could do is:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
If you find yourself needing to do this a lot, you could also use a decorator to abstract the task.
I think that you are looking for arbitrary argument lists and keyword arguments combined with super.__init__.
Give "Python's Super is nifty, but you can't use it" a read before you start down this path though.

Categories

Resources