The init values did not inherited to another class with super() - python

I built 2 classes. first class with its own init value and another class will inherit the init values from the first class. I am wondering whether I understand it correctly of the usage of super().
class testing(testing_2):
def __init__(self, name, c):
super().__init__(name, c)
def check(self):
print(super().name)
class testing_2:
def __init__(self, name, c):
self.name = name
self.c = c
tt = testing("tester", "check")
tt.check()
I thought my code was supposedly printed the "tester" because I initialize the testing class with name and c. since testing class inherit from testing_2 so we can just print the name. Am I confusing something?
My expectation is:
testing_2 will take the value from testing and we can print the values of testing_2 in testing.

Simplify it:
class A:
def __init__(self, name, c):
self.name = name
self.c = c
class B(A):
def check(self):
print(self.name)
tt = B("tester", "check")
tt.check()
The B object will have all the same things as an A object, since it inherits them. No need to implement __init__ on B if it doesn't do anything useful. You can access self.name directly, just as you would within A. The object that has that property is self. It's set on self and you can access it with self.
Remember, self is the object instance, not the class. When doing B(...), the self in A.__init__(self, ...) is actually an instance of B.
You only need to explicitly use super if you are overriding parent methods, like in:
def __init__(self, name, c):
super().__init__(name, c)
Here __init__ is overridden, and in order to execute the parent's __init__ you need to access it through super. Just self.__init__(name, c) would access the child's __init__ method and you'd call it in an endless recursive loop.

Related

How does multiple inheritance work with super?

Here are the few classes let's say, B, C, D and A, while in class A I am using super method which have class 'B' as an argument.
Where classes are as defined as below:
class B:
def __init__(self):
print('B')
class C:
def __init__(self):
print('C')
class D:
def __init__(self):
print('D')
class A(B,C,D):
def __init__(self):
super(B,self).__init__()
A()
When I am trying to initiate the class A, it should invoke class B as I passed B in super method. But it is giving me output 'C'. If I am passing D in super instead of B it's not giving any output. Why? Instead it just invoke class A and leave.
Why is this happening?
The class you pass as an argument to super should be the current class whose superclass we want to look for, not the target superclass. With your class A, the method resolution order is
A < B < C < D < object
So super(A, self).__init__() would call the method on the class following A in the MRO, which is B. Likewise, super(B, self).__init__() would call the one following B, which is C. super(C, self) would give us D, and super(D, self) would give us object (Side note: I don't know what super(object, self) does, but it seems to return a proxy object that just eats all method calls).
What you're looking for is
super(A, self).__init__()
but since you're inside a method and want to call the next method in the MRO chain anyway (the default, and most sane, behavior), you can use the 0-argument form.
super().__init__()

Overwrite class method from parent without affecting downstream classes

I would like to overwrite an inherited method in a class (see below example for __init__ method) while letting its children still use the Parents version.
I know that I could achieve the desired behaviour redefining the __init__ method in the GrandChild class or using multiple inheritance. However my question aims at a way to achieve the same with changes only to the Child class and its __init__ implementation.
(The actual use case is significantly more complex 'legacy code' with several classes on each level. The motivation of this question is therefore to achieve the desired behaviour in the respective class without having to touch the implementation of the other classes or the inheritance structure)
If this is impossible I would also appreciate an explanation to that effect.
class Parent:
def __init__(self, a,b):
self.a = a
self.b = b
def __str__(self):
return f"{self.a}, {self.b}"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a):
super().__init__(a, None)
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
parent = Parent("a","b")
child = Child("c")
# This throws a Type error right now since it calls the init method of class Child
grandchild = GrandChild("d", "e")
EDIT:
As mentioned above I am aware that I can achieve the desired behaviour in different ways such as changing the class structure (as below). However the question is really more about wether python allows doing it with changes only to the Child class. If this is actually impossible (not merely undesirable) in python, an explanation why would do more to answer my question than providing alternative implementations that change anything beyond the implementation of the Child class.
class ChildCommonFunctionality(Parent):
# Use this class for all common functionality originally provided by Child Class
pass
class Child(ChildCommonFunctionality):
# Use this class to override the init method
def __init__(self, a):
super().__init__(a, None)
class GrandChild(ChildCommonFunctionality):
# This Class should use the __init__ method of class Parent
pass
I have found a way using _init_subclass to make sure that all subclasses of Child use the constructor of Parent instead of the one defined in Child inspired by this post:
class Parent:
def __init__(self, a,b):
self.a = a
self.b = b
def __str__(self):
return f"{self.a}, {self.b}"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a):
super().__init__(a, None)
def __init_subclass__(cls):
cls.__init__ = super().__init__
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
Even though this is a bit hacky it provides the desired functionality of actually bypassing Childs init method
You could do :
class Parent:
def __init__(self, a, b = None):
self.a = a
self.b = b
def __str__(self):
return f"{self.a}, {self.b}"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a, b = None):
super().__init__(a, b) # Or None instead of b... but that's not good when called by GrandChild
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
parent = Parent("a","b")
child = Child("c")
grandchild = GrandChild("d", "e")
EDIT : you could also replace the optional parameter by a mandatory one in GrandChild :
class GrandChild(Child):
def __init__(self, a, b):
super().__init__(a, b)
This code might do the trick, adding a few lines to the suggestion of #dspr:
class Parent:
def __init__(self, a, b = None):
self.a = a
self.b = b
def __str__(self):
return f"{self.a}, {self.b}"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a, b = None):
if type(self) == Child:
if b is not None:
raise ValueError(
"Second argument is not allowed for direct use in Child class")
super().__init__(a, None) #Or (a, b) if you trust b to be None as it is here
else:
super().__init__(a, b)
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
parent = Parent("a","b")
child = Child("c")
print(child.b) # None
grandchild = GrandChild("d", "e")
print(grandchild.b) # e
child = Child("f", "g")
print(child.b) # ValueError

Calling super() without a base class

I am working through the O Reilly Python Cookbook and have been struggling with the below code. It is to with calling a method on a parent class using super():
class Proxy:
def __init__(self, obj):
self._obj = obj
# Delegate attribute lookup to internal obj
def __getattr__(self, name):
return getattr(self._obj, name)
# Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value) # Call original __setattr__
else:
setattr(self._obj, name, value)
if __name__ == '__main__':
class A:
def __init__(self, x):
self.x = x
def spam(self):
print('A.spam')
a = A(42)
p = Proxy(a)
print(p.x)
print(p.spam())
p.x = 37
print('Should be 37:', p.x)
print('Should be 37:', a.x)
The book states:
In this code the implementation of __setatrr__() includes a name
check. If the name starts with an underscore it invokes the original
implementation of __setattr__() using super(). Otherwise, it delegates
to the internally held object self._obj.
I am confused. How does super() work then if there is no explicit base class listed?
What exactly then is super() referring to?
There is always a base class; with none explicitly mentioned, Proxy inherits directly from object.
Each class defines a method-resolution order, determined recursively by its base class(es) and its ancestors. When super() gets called, it resolves to a "proxy" of the next class in the MRO of self, whether or not that class appears in the MRO of the class you are currently defining.
Consider the following classes:
class A:
def foo(self):
print("A.foo")
class B(A):
def foo(self):
super().foo()
print("B.foo")
class C(A):
def foo(self):
super().foo()
print("C.foo")
class D(C):
def foo(self):
super().foo()
print("D.foo")
class E(B,D):
def foo(self):
super().foo()
print("E.foo")
e = E()
The MRO of E is [E, B, D, C, A, object]. When you call e.foo(), you start a chain of calls in MRO order. In particular, the call to super in B.foo does not invoke A.foo, but D.foo, a method in a class B knows nothing about, as D is not an ancestor of B. But both B and D are ancestors of E, which is what matters.

How to create a subclass instance from a superclass instance

I would like to create a subclass instance from a superclass instance in Python. Suppose I have something like this:
class A():
def __init__(self, type):
...
self.type = type # this will be something that corresponds to either B or C
class B(A):
def do_something():
# this method is subclass specific
class C(A):
def do_something():
# this method is again subclass specific
I have a function that receives an instance of A, and I need to create an instance of either B or C (or D ...) based on what A's attribute type is.
I'm not sure how to go about this. Is there a way out of this or does the solution need to be redesigned?
Thank you
Start by redefining the classes A, B and C as follows. Note that you also need to pass the type value from subclass to superclass constructor via super().__init__()
class A():
def __init__(self, type):
...
self.type = type # this will be something that corresponds to either B or C
class B:
def __init__(self, type):
super().__init__(type)
def do_something(self):
print('do_something called for B')
class C:
def __init__(self, type):
super().__init__(type)
def do_something(self):
print('do_something called for C')
Then make another class which can make the decision whether to call B and C for you, and save that object locally
class User:
def __init__(self, type):
self.obj = None
if type == 'B':
self.obj = B(type)
elif type == 'C':
self.obj = C(type)
Then you can instantiate user class with different types and see that the correct do_something is called.
user_B = User('B')
user_B.obj.do_something()
#do_something called for B
user_C = User('C')
user_C.obj.do_something()
#do_something called for C
Use a dictionary that maps from types to classes.
class A():
typemap = {}
def __init__(self, typearg): # renamed this argument so it doesn't shadow standard type() function
self.type = typearg
self.typemap[typearg] = type(self)
def create_child(self, *args):
return typemap[self.type](*args)
When the constructor runs, type(self) gets the subclass of the object being created. This is then stored in the dictionary, so we can look it up using self.type.
The create_child() looks up the class in the dictionary, and calls it to create a new instance of that child class.

Only declare variable if doesn't exist yet in the initialization of a class

I'm working for the first time with OOP in a python project. I have 3 classes: PRA, GDB and XLS.
PRA = Main Class
GDB = Responsible for control databases
XLS = Responsible for control xls(x)
UserInput = Responsible for validate user input
How i'm doing:
PRA:
class PRA(GDB, XLS, UserInput):
__init__():
self.gdb_file, self.xls_file = self.ask_questions() # <--- ask_questions is inside the UserInput class.
self.do_something()
XLS:
class XLS:
do_something():
print(self.xls_file)
__init__(self, xls_file):
self.xls_file = xls_file
GDB:
class GDB:
__init__(self, gdb_file):
self.gdb_file = gdb_file
I would like to know if is a bad practice to initialize the gdb_file and xls_file inside the PRA.init, and if not, how can i run the initialization of self.xls_file and self.xls_gdb only in the PRA?
This is not really an answer, but more of an example of code reuse. I'm not sure if this is super Pythonic, though. Anyhow, take a look at this example:
import inspect
class A:
def __init__(self):
self.a = 1
class B:
def __init__(self):
self.b = 2
class C(A, B):
def __init__(self):
for super_class in inspect.getmro(type(self))[1:]:
super_class.__init__(self)
c = C()
print(c.a, c.b)
A bit OT but you may want to think twice about your class names... They are really terrible.
Now wrt/ your question... Technically, the way to handle proper initialisation of a parent class is to call it's initialiser from the child class, ie:
class Parent(object):
def __init__(self, name):
self.name = name
class Child(Parent):
def __init__(self, name, age):
super(Child, self).__init__(name)
self.age = age
You can also explicitly name the parent class, ie:
class Child(Parent):
def __init__(self, name, age):
Parent.__init__(self, name)
self.age = age
but it has a couple drawbacks, like harcoding the parent class in the call (so you have two or more places to edit if you change the parent class), and, more important, not taking care of proper resolution of the "diamond" problem in multiple inheritance (ie D child of B & C, B child of A, C child of A).
Actually, the only reason to not use super() would be when parent classes constructor are not compatible, but this is a huge design smell - if you have two or more parent classes that are not compatible, then you're certainly not using inheritance the right way. And that's actually the case in your example: your "main" class "is" not a "Database controller", it uses a Database controller instead, so you want to use composition, not inheritance
class ServiceA(object):
# code here
class ServiceB(object):
# code here
class Main(object):
def __init__(self, arg1, arg2, arg3):
self.arg1 = arg1
self.service_a = ServiceA(arg2)
self.service_b = ServiceB(arg3)
def run(self):
something = self.service_a.ask(question)
self.service_b.do_something_with(something)
Also you may want to avoid user interactions and costly resource acquisitions in constructors... Better use an entry point method for the first case (like app = MyApp(params); app.run()`) and lazy initialization for the second case (wait until you need the resource to acquire it, use a private attribute as cache an a public property that will take care of acquisition on first access).
[edit]
Someone suggested a dirty hack based on introspection, ie:
import inspect
class A:
def __init__(self):
self.a = 1
class B:
def __init__(self):
self.b = 2
class C(A, B):
def __init__(self):
for super_class in inspect.getmro(type(self))[1:]:
super_class.__init__(self)
c = C()
print(c.a, c.b)
This is definitly not something to do.
If all your parent classes have compatible constructors and properly use super calls, then all you have to do is using super instead:
class A(object):
def __init__(self):
super(A, self).__init__()
self.a = "a"
class B(object):
def __init__(self):
super(B, self).__init__()
self.b = "b"
class C(A, B):
def __init__(self):
super(C, self).__init__()
self.c = "c"
c = C()
print c.a, c.b, c.c
This also takes care of the "diamond inheritance" problem by calling parent classes in the right order:
class D(C, A):
def __init__(self):
super(D, self).__init__()
self.d = "d"
d = D()
print "d : ", d.a, d.b, d.c, d.d
Note that unless only one of the parent classes is a "proper" class (with state) and all other are mixin classes (stateless classes that only add functionalities), multiple inheritance is more often than not a design smell (as well as a maintainance hell). Since Python doesn't use static typing, inheritance is mostly used for implementation inheritance, and for a lot of cases composition/delegation (or just plain composition as in the OP case) is a better solution than implementation inheritance.

Categories