How can I set override default kwargs in a parent class? - python

Let's say I have the following parent and child classes:
class A(object):
def __init__(self, *args, **kwargs):
self.a = kwargs.get('a', 'default_A')
self.b = kwargs.get('b', 'default_B')
class B(A):
a = "override_A"
def __init__(self, *args, **kwargs):
super(B, self).__init__(**kwargs)
b = B()
print b.b # this is "default_B", as expected
print b.a # I expected this to be "override_A"
What am I doing wrong here? I've tried to understand how inheritance works via answers like this one but haven't found something that describes this specific requirement.

You're mixing class and instance variables. B.a is a class variable, which is shadowed by the instance variable set in A.__init__().
You could for example use dict.setdefault():
class B(A):
def __init__(self, *args, **kwargs):
# If the key 'a' exists, this'll be effectively no-operation.
# If not, then 'a' is set to 'override_A'.
kwargs.setdefault('a', 'override_A')
super(B, self).__init__(**kwargs)

Related

python class B inherit class A with all its methods but change one method only

Let's say class A has 10 methods. Some of the methods are private and it has private attributes as well. I want to create class B so I can change last method only without duplicating the code for the rest of the methods. My example is below. At the moment I am unable to achieve it with such inheritance as I get AttributeError: 'B' object has no attribute '_B__c'
class A:
def __init__(self, a=1, b=2):
self.a = a
self.b = b
self.__foo()
def __foo(self):
self.__c = self.a + self.b
def get_data(self):
return self.__c
class B(A):
def __init__(self, *args, **kwargs):
super(B, self).__init__(*args, **kwargs)
self.__c = self.__modify_data()
def __modify_data(self):
self.__c += 10000
def get_data(self):
return self.__c
b = B(a=5, b=10).get_data()
Question 2:
Can I achieve it with use of *args so I do not have to repeat all the arguments?
EDIT:
Please see my updated code above.
I believe private attributes causes the problem.
Can I solve it with still using private?
class A(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
self.__foo()
def __foo(self):
self._c = self.a + self.b
def get_data(self):
return self._c
class B(A):
def __init__(self, *args, **kwargs):
super(B, self).__init__(*args, **kwargs)
self.__modify_data()
def __modify_data(self):
self._c += 10000
b = B(a=5, b=10).get_data()
print(b)
Output:
10015
Changing _c to __c gives AttributeError: 'B' object has no attribute '_B__c'
Yes, the __ is causing the trouble by making variable c inaccessible in children, which is good because the private variable of parents should not be allowed to edit by the children class.

Python: why should last class in MRO have zero parameter in its super's __init__ call or there would be runtime exception

Suppose the class hierarchy is like below, and the parameters inside args are passed along the inheritance chain so that all classes get the parameter they need:
class A(object):
def __init__(self, **args):
print('A.__init__')
self.a = args['a']
super(A, self).__init__(**args)
class B (object):
def __init__(self, **args):
print('B.__init__')
self.b = args['b']
# super(B, self).__init__(**args)
class C(A, B):
def __init__(self, **args):
print('C.__init__')
self.c = args['c']
super(C, self).__init__(**args)
c = C(a=1, b=2, c=3)
print(c.__dict__)
If class B calls super's __init__ with args, that is, uncomment this line
# super(B, self).__init__(**args)
, an exception is raised:
..., in __init__
super(B, self).__init__(**args)
TypeError: object.__init__() takes no parameters
It seems that the last class in the MRO couldn't call super's __init__, or could only call it with zero parameter. Would anyone explain why?
Suppose coder 0 writes class A and B, and then coder 1 writes class C to extend A & B. Since coder 0 doesn't know the actual MRO, how could he decide which class should call super's __init__ with args and which one shouldn't?
Or would anyone share the best practice of multiple inheritance together with parameter passing?
The base class of A and B is object. object is a class that takes no parameters, because it doesn't do anything with them. (You can't write object(1), can you?)
The best practice for solving this is to remove the consumed args before passing them along.
class A(object):
def __init__(self, **args):
print('A.__init__')
self.a = args.pop("a")
super(A, self).__init__(**args)
class B (object):
def __init__(self, **args):
print('B.__init__')
self.b = args.pop("b")
super(B, self).__init__(**args)
class C(A, B):
def __init__(self, **args):
print('C.__init__')
self.c = args.pop("c") # permanently removes 'c' from args
super(C, self).__init__(**args)
c = C(a=1, b=2, c=3)
print(c.__dict__)

Initialize a subobject of an object in Python

Let we have an object x of class A (which may have "private" fields, which I do not know) and I am creating a class B:
class B(A):
# ...
Now I want to create an object y of class B, whose A part is equal to x.
How to do this in Python (both 2.x and 3.x)?
I don't know if I understood well, but I think you need this in the class B:
class A():
def __init__(self, variable):
self.variable = variable
class B(A):
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
Everytime you instantiate a new object from B, it will initialize all attributes in A.__init__ method. If you need the list of attributes:
y = B(variable="123...")
attrs = y.__dict__.keys()

How to determine the relationship between classes?

I have two classes:
class A(object):
"""Instance of this class must by only one"""
def some_function(self):
print "A function"
[... other functions ...]
class B(object):
"""Many instances of this class"""
[... functions ...]
And I want to init only one object of class A from class B, I write this:
class B(object):
a_instance = A()
b_instances = []
def __init__(self, *args, **kwargs):
B.a_instance.some_function()
B.b_instances.append(self)
super(B, self).__init__(*args, **kwargs)
def update(self):
print "Update instance"
#classmethod
def super_function(self):
print "My super B function"
for instance in B.b_instances:
instance.update()
Is this correct?
And second question: How to call "super_function" from instance of class A?

Calling superclass constructors in python with different arguments

class A():
def __init__( self, x, y):
self.x = x
self.y = y
class B():
def __init__( self, z=0):
self.z = z
class AB(A,B):
def __init__( self, x, y, z=0):
?
How can I make the constructor of AB call the constructors for A and B with the proper arguments?
I've tried
class AB(A,B):
def __init__( self, x, y, z=0):
A.__init__(x,y)
B.__init__(z)
but this gives me an error.
Other answers suggested adding self to the first parameter.
But usually invocations of __init__ in parent classes are made by super.
Consider this example:
class A(object):
def __init__(self, x):
print('__init__ is called in A')
self.x = x
class B(object):
def __init__(self, *args, **kwargs):
print('__init__ is called in B')
super(B, self).__init__(*args, **kwargs)
class AB(B, A):
def __init__(self, *args, **kwargs):
print('__init__ is called in AB')
super(AB, self).__init__(*args, **kwargs)
AB class contains an order in which constructors and initializators should be called:
>>> AB.__mro__
(<class '__main__.AB'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
See, that first AB's __init__ is invoked, then B's, then A's, and then object's.
Let's check:
>>> ab = AB(1)
__init__ is called in AB
__init__ is called in B
__init__ is called in A
But these calls through this chain are made by super. When we type super(AB, self), it means: find then next class after AB in __mro__ chain of self.
Then we should invoke super in B, looking for the next class in the chain after B: super(B, self).
It's important to use super and not call manually A.__init__(self,...), etc., as it may lead to problems later. Read this for more info.
So, if you stick with super, then there is a problem. __init__ methods in your classes expect different parameters. And you can't know for sure the order in which super will be invoking methods in these classes. The order is determined by C3 algorithm at the time of class creation. In subclasses another classes may get in-between of the call chain. So you can't have different parameters in __init__, as in this case you will have always to consider all inheritance chain to understand how __init__ methods will be called.
For example, consider adding C(A) and D(B) classes and CD subclass of them. Then A will no longer be invoked after B, but after C.
class A(object):
def __init__(self, *args, **kwargs):
print('__init__ is called in A')
super(A, self).__init__(*args, **kwargs)
class B(object):
def __init__(self, *args, **kwargs):
print('__init__ is called in B')
super(B, self).__init__(*args, **kwargs)
class AB(B,A):
def __init__(self, *args, **kwargs):
print('__init__ is called in AB')
super(AB, self).__init__(*args, **kwargs)
class C(A):
def __init__(self, *args, **kwargs):
print('__init__ is called in C')
super(C, self).__init__(*args, **kwargs)
class D(B):
def __init__(self, *args, **kwargs):
print('__init__ is called in D')
super(D, self).__init__(*args, **kwargs)
class CD(D,C):
def __init__(self, *args, **kwargs):
print('__init__ is called in CD')
super(CD, self).__init__(*args, **kwargs)
class ABCD(CD,AB):
def __init__(self, *args, **kwargs):
print('__init__ is called in ABCD')
super(ABCD, self).__init__(*args, **kwargs)
>>> abcd = ABCD()
__init__ is called in ABCD
__init__ is called in CD
__init__ is called in D
__init__ is called in AB
__init__ is called in B
__init__ is called in C
__init__ is called in A
So I think it's a good idea to think about using delegation instead of inheritance here.
class AB(object):
def __init__(self, x, y, z=0):
self.a = A(x,y)
self.b = B(z)
So, you just create a and b instances of A and B classes inside AB object. And then may use them as you need through methods by referring to self.a and self.b.
To use or not delegation depends on your case which is not clear from your question. But it may be an option to consider.
You didn't pass self.
class AB(A, B):
def __init__(self, x, y, z=0):
A.__init__(self, x, y)
B.__init__(self, z)
Note that if this inheritance hierarchy gets more complicated, you'll run into problems with constructors not executing or getting reexecuted. Look into super (and the problems with super), and don't forget to inherit from object if you're on 2.x and your class doesn't inherit from anything else.

Categories