Constructor call in inheritance - python

I have a very naïve question.
I have the following code in Python:
class A:
def __init__(self):
print("constructor A")
class B(A):
pass
ob = B()
It gives the following output:
constructor A
Similarly in the following code:
class A:
def __init__(self,name):
self.name = name
print(f'{self.name}')
class B(A):
def __init__(self):
print('Class B constructor')
ob1 = B()
ob2 = B('abc')
Shouldn't the output be:
class B constructor
abc
In fact it gives the following error:
TypeError: B.__init__() takes 1 positional argument but 2 were given
Isn't def __init__(self,name) of class A gets inherited in class B and can't we call it using ob2 = B('abc')?

Just to mention, __init__ is not constructor, it's initializer. It initializes the newly created object returned from __new__.
No, it Python attributes(including methods) of the classes are resolved using mro(Method Resolution Order), Whenever Python finds that, it stops going further.
In this example, Python didn't find fn in C, so it checks B, then A, and now it finds it. (If it didn't find it in A, it checks object class which is the last class in every class's MRO. In that case it raises AttributeError because object doesn't have fn either)
class A:
#staticmethod
def fn():
print("fn in A")
class B(A):
pass
class C(B):
pass
print(C.mro())
C.fn()
output:
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
fn in A
If for example B has defined fn, it stops there:
class A:
#staticmethod
def fn():
print("fn in A")
class B(A):
#staticmethod
def fn():
print("fn in B")
class C(B):
pass
print(C.mro())
C.fn()
output:
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
fn in B
Same thing happens to __init__.
If you want your code to work, you need to call the A's initializer inside the B's initializer. Note that the signature of the B's __init__ should compatible with the A's __init__. By compatible I mean it should take at least the number of parameters that A's __init__ takes, because you're going to pass those parameters to A.__init__:
class A:
def __init__(self, name):
print("Class A initializer")
self.name = name
print(f"{self.name}")
class B(A):
def __init__(self, name):
super().__init__(name)
print("Class B initializer")
ob2 = B("abc")

If you want the __init__() in A to be called, you have to do it yourself:
class A:
def __init__(self, name):
self.name = name
print(f'{self.name}')
class B(A):
def __init__(self, name):
print('Class B constructor')
super().__init__(name):
ob2 = B('abc')
Output
class B constructor
abc

You are trying to override constructor inside class B. class B inheritances from class A. And class A constructor except name argument. You don't need add init method inside B class here. One of the advantage using OOP is to reduces repetition. You decided to which parameters parent class will have. If you don't use parent class attributes in child class then there is no logic to create parent class. In short, parent class store attributes and methods in common, and if you want, you can add more attributes and method to child classes.
Code snippet will be like this
class A:
def __init__(self,name):
self.name = name
print(f'{self.name}')
class B(A):
def __init__(self,name):
super().__init__(name, ...) # here you can add attributes that is specific to B class
print('Class B constructor')
ob2 = B('abc')

class A:
def __init__(self, name):
self.name = name
print(f'{self.name}')
class B(A):
def __init__(self, *args):
if len(args) == 1:
super(B, self).__init__(name=args[0])
else:
print('Class B constructor')
ob1 = B()
ob2 = B('abc')
*args can control how many objects you enter, in that situation we can enter 1 or 0 objects.
if you enter 0 objects in your constructor, it will generate ('Class B constructor'), but if you enter 1 object, with super() method you call the A class constructor and name is equals to object, that is entered in the constructor.

it's because you __init__ take only one argument self which is always passed you need to add name to the definition of B
class A:
def __init__(self,name):
self.name = name
print(f'{self.name}')
class B(A):
def __init__(self, name):
print('Class B constructor')
ob2 = B('abc')

Related

python instantiate child instance from parent object

I have an object which is instantiated from parent class(it will b variable at below example code)
and i want to use this object like a child class instance without knowledge about the member variable of parent class
is there any recommendation?
class A:
def __init__(self):
pass # some member variables are assigned
pass
class B(A):
def test(self):
print("test")
pass
b = A()
b.test() # error
You can do this by setting __class__ of b to B. But read this first:
https://stackoverflow.com/a/13280789/6759844
class A:
def __init__(self):
pass # some member variables are assigned
pass
class B(A):
def test(self):
print("test")
pass
b = A()
b.__class__ = B
b.test() # error

Python Class inheritance trouble

When I try to do this I get the error NameError: global name 'name' is not defined is there an easy work around?
class C(object):
def __init__(self, name):
self.name = name
class D(C):
def __init__(self):
C.__init__(self, name)
obj1 = D()
In class D, when you initialize it from C, you're not passing a value to the initialization of C, so it raises a NameError when it tries to pass it. You either need to allow D to take a value name and then pass it to C,
class C(object):
def __init__(self, name):
self.name = name
class D(C):
def __init__(self, name):
C.__init__(self, name) # name must be passed on initialization of D.
or define a default value for name in D.
class C(object):
def __init__(self, name):
self.name = name
class D(C):
def __init__(self, name='Monty Python'):
C.__init__(self, name) # allows you to only pass a name if you want to.
Instead of using the parent class name use super() here so that later if you need to you can enable things like mixins, interfaces, abstract classes, etc.
Then you can also allow for those keyword arguments in the parent class to be used in the child class like this:
class D(C):
def __init__(self,**kwargs):
super(D,self).__init__(**kwargs)
Example usage:
In [349]: obj1 = D(name='test')
In [350]: obj1.name
Out[350]: 'test'
never ever hard code the metho you inheriting instead of do super() by this if you want to change the method just change in the class name will be sufficient
there is no need to give argument's to super() in python3
class C(object):
def __init__(self, name):
self.name = name
class D(C):
def __init__(self, name):
super().__init__(self, name)

how to extract the class that super().__init__ comes from?

Imagine a class MyMixInClass that is being used in a multiple inheritance hierarchy. when using super() to call some method, is there some way to inspect or drill in to extract the class that this method came from?
example:
class MyMixInClass:
def __init__(self):
initfunc = getattr(super(), '__init__')
# can we figure out which class the __init__ came from?
For each class in the mro sequence, you can check if there is an __init__ method in the class __dict__:
class A:
def __init__(self):
pass
class B(A):
def __init__(self):
super().__init__()
class C(A):
pass
class D(B, C):
pass
if __name__ == '__main__':
for cls in D.__mro__:
if '__init__' in cls.__dict__:
print(f'{cls.__name__} has its own init method', end='\n')
else:
print(f'{cls.__name__} has no init method', end='\n')
output:
D has no init method
B has its own init method
C has no init method
A has its own init method
object has its own init method
In this output, the first class having an __init__ method (here B), is the one called by super().__init__() in D()

printing an argument from another class which inherited from different class

Apparently, I have 3 different classes; a , b and c.
class a: contain the self.king which was inherited from class b(entered by user)
class b: prompt user to enter the king value and this will be pass to class a
class c: inherit from class a that have the entered king values from class b and which was then pass to class a.
Example of codes:
class a(object):
def __init__(self, king):
self.king = king
class b(a):
def __init__(self, king):
super(b,self).__init__(king=king)
class c(a):
def __init__(self):
super(c,self).__init__()
print(self.king)
if __name__ == "__main__":
bb = b(king="madsfjsfdsdfn")
cc = c()
cc
Did I do anything wrong here? I get an error such as :
TypeError: __init__() missing 1 required positional argument: 'king'
I thought the value entered in class b has been passed to class a. Since it has been passed to class a, then class c by using super.init() will inherits its values and print it accordingly. Did I do it wrongly and understand super() function wrongly?
When user pass value and only 2 class are present:
class a(object):
def __init__(self, king):
self.king = king
class b(a):
def __init__(self, king):
super(b,self).__init__(king=king)
print(self.king)
if __name__ == "__main__":
bb = b(king="madsfjsfdsdfn")
bb
Then my output is correct:
madsfjsfdsdfn
This example I think meets your specification. The only class that needs a constructor (init) is the one where you are storing a value in an instance of the class (creating an object to store a king value).
#Child classes inherit from parent classes
class b():
def prompt_for_king():
return input("Enter king value!\n")
class a(b):
def __init__(self):
self.king = b.prompt_for_king()
class c(a):
pass
if __name__ == "__main__":
#instantiate class
object_c=c()
print (object_c.king)
This will be the most similar to my current solving problem and what I would like to get.
My goal is actually to find a way to pass this stored value which was called in class b(from my example) and passed as instance of class a and then inherit it to class c
Code:
class a(object):
def __init__(self, king):
self.yes = king
def passing(self):
return self.yes
class c(object):
def __init__(self, king):
self.yes = king
def ps(self):
print(self.yes)
class b(a,c):
def __init__(self, king):
super().__init__(king = king)
if __name__ == "__main__":
bb = b(king="aaa")
bb.ps()
You're looking at it backwards. When you define class b(a): it means that b inherits from a and not the other way around, so when you call super on c's __init__ you're calling a.
Basically what happens when you do c() (the same as c.__init__()), the first line of c's __init__ function becomes a.__init__() (the same as a()), and as the error says a's __init__ function takes the king argument and can't be called without such argument.
Besides there's a conceptual misunderstanding between Classes and Objects, for instance in your code a, b and c are classes (not to mention classes should always start with a capital letter) while bb and cc are objects. Once you define a class you won't be able to inherit attributes modified outside the class definition.
EDIT:
After reading several comments I think that what you want is a static attribute king, it's important to know that self keyword only exists within objects, so by doing self.king = king you're saying only a's objects will have a king attribute, and not a class itself.
Say we have a class A:
class A(object):
p1 = 'static'
def __init__(self):
self.p2 = 'object'
I can access A.p1 or A().p1 and get 'static', A().p2 will give me 'object' and A.p2 will give me an error because A it's not an object. If a class inherits from A it will only inherit p1, as p2 is not static.
I understand that what you want to do is to change this p1 from instanciating a class that inherits from A, say B:
class B(A):
def __init__(self, param):
super(B, self).__init__()
super.p1 = param
the thing is super is not actually the parent class, but a built in function that returns an object, so doing this yields:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-12-ca4dc9978d74> in <module>
----> 1 B(2)
<ipython-input-10-af935ee24ae2> in __init__(self, param)
2 def __init__(self, param):
3 super(B, self).__init__()
----> 4 super.p1 = param
5
6
TypeError: can't set attributes of built-in/extension type 'super'
you'd have to do
class B(A):
def __init__(self, param):
super(B, self).__init__()
A.p1 = param
explicitly modifying the A class so that from there, objects which class inherit from A would have the new parameter, lets say:
class C(A):
def __init__(self):
super(C, self).__init__()
print(self.p1)
you can do:
print(A().p1)
B('modified')
C()
and see that even when the first line will print 'static', the last one will print 'modified'.

Access class which object is being called from within method belonging to object

I wonder if there is a way in Python to access the class which the object which the method belongs to is being called from. For example:
class A:
def __init__(self):
self.b = B()
def foo(self):
print('A')
class B:
def bar(self):
<something here>.foo()
a = A()
a.b.bar()
Basically I would like B's method bar to invoke A's method foo. And if b was an attribute of some other class C, to invoke C's version of foo instead.
You could add a reference to the class which instantiates B:
class A:
def __init__(self):
# pass self while instantiating B
self.b = B(self)
def foo(self):
print('A')
class B:
def __init__(self, rel_obj):
self.rel_obj = rel_obj
def bar(self):
self.rel_obj.foo() # access foo() using self.rel_obj
Similarly, you could pass an object of class C to invoke C's version of foo method.

Categories