printing an argument from another class which inherited from different class - python

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'.

Related

Constructor call in inheritance

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')

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__()

Understanding Nested Inheritance in Python

Here is a simplified code of my main code illustrating the behaviour I obtain.
Suppose I have a main class (MAIN) and two classes (A,B) inheriting from it. This main class has a method which is overwriten by A but not by B, which means that B inherits the method from main.
Then I have a class D which inherits from A and from B, and has a method which calls the aforementioned method. From what I have understood in the way multiple inheritance work, if I define D as class D(A,B) then if A and B have a shared method, calling D.method() will call A.method, and vice-versa (i.e if class D(B,A) then B.method is called. The following code exemplifies this text.
class MAIN(object):
def __init__(self):
pass
def print(self):
print('HELLO MAIN')
class A(MAIN):
def __init__(self):
pass
def print(self):
print('HELLO A')
class B(MAIN):
def __init__(self):
pass
class C(A,B):
def __init__(self):
pass
def Cprint(self):
self.print()
c = C()
c.Cprint()
class C(B,A):
def __init__(self):
pass
def Cprint(self):
self.print()
c = C()
c.Cprint()
However this code always print 'HELLO A', i.e even in the case class C(B,A) I don't get a HELLO MAIN as I would expect. What is happening here? Thanks so much in advance
The mro is (C, A, B, MAIN) with class C(A, B) and (C, B, A, MAIN) with class C(B, A). In both cases, A is before MAIN. B doesn't define .print, so it doesn't matter.
The method uplooks works like this: (pseudo code)
def find_attribute(obj, name):
if name in obj.__dict__:
return obj.__dict__[name]
mro = type(obj).__mro__
for cls in mro:
if name in cls.__dict__:
return cls.__dict__[name] # (Here a bit more magic for descriptors happens)
raise AttributeError(name)
For the classes this is what their __dict__ look like:
MAIN.__dict__ = {"print": <method MAIN.print>}
A.__dict__ = {"print": <method A.print>}
B.__dict__ = {}
C.__dict__ = {"Cprint": <method C.Cprint>}
As you can see, B does not have a print defined, so in mro=(C, B, A, MAIN) the first print that does get found is in A.
You are inheriting the Class A everywhere and class A overrrides Main functions print() thats why you dont get the "HELLO MAIN"
class C(B):
def __init__(self):
pass
def Cprint(self):
self.print()
inherit only B class which does not overrides Main class print function then you will get the HELLO MAIN output

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.

python class call super's method instead of inherited

I have the following problem
class A:
def __init__(self):
self.window = "<I am window class>"
class B(A):
def __init__(self):
super().__init__()
def createObject(self):
print(self.window) # it works here
self.c = C()
def drawRect(self):
print(self.window) # does not work when called from class C
class C(B):
def __init__(self):
self.draw()
def draw(self):
super().drawRect()
app = B()
app.createObject()
I have figured that, when I call the super().drawRect() function in class C
it sends the <__main__.C object at 0x0000023D99FA6748> as self.
How can I make class C, in function draw() send <__main__.B object at 0x0000017A2BA95358> in the super().drawRect()
I am asking because in the non-simplified code when I call the function from the B class itself, it prints the self.window, but when called from the C class it prints None
how can this be fixed?
what am I doing wrong?
There is no B object when you create an instance of C, there is only a C instance that happens to inherit from B.
So the answer to your question:
How can I make class C, in function draw() send <__main__.B object at 0x0000017A2BA95358> in the super().drawRect()
Is: You can't.
You could create a new B object (but that would defy the purpose of inheritance) alongside your C object.
But it seems more reasonable that you should just call super().__init__ in C.__init__:
class C(B):
def __init__(self):
super().__init__()
self.draw()
def draw(self):
super().drawRect()
It would be weird not to do that anyway. Because inheritance sort of implies that you extend rather than override completely.
But given the fact that you do self.c = C() in your B.createObject method and C inherits from B you should probably figure out another way (e.g. composition) instead of inheritance:
class A:
def __init__(self):
self.window = "<I am window class>"
class B(A):
def __init__(self):
super().__init__()
def createObject(self):
print(self.window) # it works here
self.c = C(self)
def drawRect(self):
print(self.window) # does not work when called from class C
class C(object): # don't inherit from B
def __init__(self, obj):
self.obj = obj
self.draw()
def draw(self):
self.obj.drawRect()
app = B()
app.createObject()
However this will create a reference cycle because you store the C instance as attribute of your B instance and the B instance as attribute of your C instance. It's also possible that you simplified the code too much, maybe the real code prevents composition in some way. However given the current code it seems like inheriting from B is a bad idea for your class C given that it overrides Bs behavior completely and doesn't use any of it's attributes (which it can't anyway because you overrode the methods).
you need to call to
super().__init__()
in class C constructor
Charlie

Categories