Understanding Nested Inheritance in Python - 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

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

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

python multiple inheritance, calling base class function

I was just trying something with multiple inheritance in python. I come up with this
class ParentOne:
def foo(self):
print("ParentOne foo is called")
class ParentTwo:
def foo(self):
print("ParentTwo foo is called")
class Child(ParentOne, ParentTwo):
# how is this working
def call_parent_two_foo(self):
super(ParentOne, self).foo()
# This does not work
def call_parent_foo(self):
super(ParentTwo, self).foo()
def call_super_foo(self):
super(Child, self).foo()
def foo(self):
print("Child foo is called")
if __name__ == "__main__":
child = Child()
child.foo()
child.call_super_foo()
child.call_parent_two_foo()
# child.call_parent_foo() #This gives the below error
# super(ParentTwo, self).foo()
# AttributeError: 'super' object has no attribute 'foo'
and it gives the following output
Child foo is called
ParentOne foo is called
ParentTwo foo is called
I am getting confused as to how calling of super(ParentOne, self).foo() is evaluated in this case. As per my understanding ParentOne class does not have any idea of the methods and attributes of ParentTwo class. How does super works in case of multiple inheritance
Python constructs a method resolution order (MRO) when it builds a class. The MRO is always linear. If python cannot create a linear MRO, then a ValueError will be raised. In this case, your MRO probably looks like:
Child -> ParentOne -> ParentTwo -> object
Now when python see's a super(cls, self), it basically looks at self and figures out the MRO. It then uses cls to determine where we are currently at in the MRO and finally it returns an object which delegates to the next class in the MRO. So, in this case, a super(Child, self) call would return an object that delegates to ParentOne. A super(ParentOne, self) class would return an object that delegates to ParentTwo. Finally a super(ParentTwo, self) call would delegate to object. In other words, you can think of super as a fancier version of the following code:
def kinda_super(cls, self):
mro = inspect.getmro(type(self))
idx = mro.index(cls)
return Delegate(mro[idx + 1]) # for a suitably defined `Delegate`
Note that since super(ParentTwo, self) returns a "Delegate" to object, we can see why you're getting an AttributeError when you try super(ParentTwo, self).foo() -- Specifically the reason is because object has no foo method.
class X1:
def run(self):
print("x1")
class X2:
def run(self):
print("x2")
class X3:
def run(self):
print("x3")
class X2:
def run(self):
print("x2")
class Y(X1, X2, X3):
def run(self):
print("y")
Given an instance:
y = Y()
To call base class function:
super(Y,y).run()
super(X1,y).run()
super(X2,y).run()
y.run()
Output
x1
x2
x3
y
Similarity,
super(Y, y).run()
for cls in y.__class__.__bases__:
if(cls != X3):
super(cls,y).run()
y.run()
Output
x1
x2
x3
y
You may understand Child(ParentOne, ParentTwo) as two separate inheritances within a chain: Child(ParentOne(ParentTwo)). Actually, ParentOne doesn't inherit ParentTwo, they are two separate classes, but the method super works like there's a chain of inheritances (in case of multiple inheritance only). I like this example to understand better what's going on (for Python 3.x):
class P:
def m(self):
print("P")
class A(P):
def m(self):
super().m() # -> B, if we inherit like C(A, B)
print("A")
class B(P):
def m(self):
super().m() # -> P, if we inherit like C(A, B)
print("B")
class C(A, B):
def m(self):
super().m() # -> A
print("C")
A.m(self)
B.m(self)
c = C()
c.m()
It also considers a case if two parents inherit one base class. The script above prints:
P
B
A
C
P
B
A
P
B

Why I can't instantiate a instance in the same module?

Suppose my module is myclass.py, and here is the code:
#!/usr/bin/env python
# coding=utf-8
class A(object):
b = B()
def __init__(self):
pass
class B(object):
pass
and import it
In [1]: import myclass
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1-e891426834ac> in <module>()
----> 1 import myclass
/home/python/myclass.py in <module>()
2 # coding=utf-8
3
----> 4 class A(object):
5 b = B()
6 def __init__(self):
/home/python/myclass.py in A()
3
4 class A(object):
----> 5 b = B()
6 def __init__(self):
7 pass
NameError: name 'B' is not defined
I know that if I define the class B above the class A, it is ok, there is no error. But, I don't want do that, are there any other methods to solve this. And I know that in C, there is function declaration.Thank you!
The class definition is a statement. When statement AA is executed, The statement of BB is not executed yet. Therefore, There is no class B yet and you get NameError: name 'B' is not defined
class A(object):
b = B() # <== AA
def __init__(self):
pass
class B(object): # <== BB
pass
To fix it:
You can change the order of classes:
class B(object):
pass
class A(object):
b = B()
def __init__(self):
pass
You can move the statement which use the class B to classmethod and call it after the the defintion of class B:
class A(object):
#classmethod
def init(cls):
cls.b = B()
def __init__(self):
pass
class B(object):
pass
A.init()
It should work if you do it like so:
class A(object):
def __init__(self):
self.b = B()
class B(object):
pass
EDIT: You can do it like this if you want to write all the definitions of the class after you have written class A.
class B:
pass
class A(object):
b = B()
def __init__(self):
pass
class B(object):
def __init__(self):
pass
EDIT 2: Ignore the above solution, it doesn't work.
Is there any good reason to do what you are doing? In general this is quite dangerous pattern in Python.
In your case
class A(object):
b = B()
def __init__(self):
pass
You are binding an instance of B to the class A, which means that every instance of class A will share the same instance of class B. It's a case you must then handle properly.
In general you don't want this, If you want each instance of A to be related to an instance of B, you must make the assignment inside __init__
class A(object):
def __init__(self):
self.b = B()
In these case it doesn't meter where class B is defined, since it's instantiated at run time.
Again beware that the semantic is very different in the two cases (if you know Java, the former is more like defining a static attribute).
About:
And I know that in C, there is function declaration
You shouldn't make too much parallels with a language like C, which is very different on many aspects, most important: it's a compiled language, that means that you code is parsed in it whole before being translated to machine language, that's why you can make function declaration and have your namespace populated regardless of the order you define things.
Python is an interpreted language, which means basically that each statement is translated when it's called and a class declaration is called when the module is imported.
So to recap: if you really need a class bound instance, you have to declare class B before class A, else you must instantiate B inside __init__, then you can declare B wherever you want (since it's called at runtime).

Initialize class but with different parents in python

In python, is there a way, when initializing a Class, to change the superclass in function of the value of a class attribute? Here's an example of what I want to do. First I have theses classes:
class A(object):
pass
class B(A):
# extend and override class A
pass
class C(A or B):
# extend and override class A
pass
Secondly, I want to create other classes that inherit from Class C but in some cases I want C to inherit from A and on other cases, inherit from B:
class D(C):
# C inherit only from A
from_B = False
class E(C):
# C inherit from B because attribute from_B = True
from_B = True
I tried with metaclass but that was setting the base class of C (to A or B) for all subclasses (D, E, ...) at the initialization of the first subclass. So, if the first subclass to be initialize had from_B = True, all subclasses of C had C(B) as parent whatever from_B was set. My code was something like this:
class MetaC(type):
def __new__(cls, name, bases, attrs):
if C in bases and getattr(attrs, 'from_B', False):
C.__bases__[C.__bases__.index(A)] = B
return super(MetaC, cls).__new__(cls, name, bases, attrs)
class C(A):
__metaclass__ = MetaC
My goal is to avoid the duplication of the code of the C class and keeping the possibility to have or not the added functionalities of the B class. I should mention that I don't have control on A and B classes.
UPDATE
I think I got it with this metaclass (code is a bit rough at the moment):
class MetaC(type):
def __new__(cls, name, bases, attrs):
for base in bases:
if base.__name__ == 'C':
if attrs.has_key('from_B'):
list_bases = list(base.__bases__)
list_bases[list_bases.index(A)] = B
base.__bases__ = tuple(list_bases)
elif B in base.__bases__:
list_bases = list(base.__bases__)
list_bases[list_bases.index(B)] = A
base.__bases__ = tuple(list_bases)
break
return super(MetaC, cls).__new__(cls, name, bases, attrs)
UPDATE 2
This solution doesn't work because I'm always modifying the base class C. So, when a subclass is instanciated it will use the C class in it's current state.
I ended by using cooperative multiple inheritance. It works fine. The only drawback is that we need to be sure that for methods that need to be call on many parent classes (like methods that are present in A and B and C), there's a super() call in each method definitions of each classes and that they have the same calling signature in every case. Fortunately for me my B classes respect this.
Example:
class A(object):
some_method(arg1, arg2, karg1=None):
do_some_stuff(arg1, arg2, karg1)
class B(A):
# extend and override class A
some_method(arg1, arg2, karg1=None):
super(B, self).some_method(arg1, arg2, karg1)
do_more_stuff(arg1, arg2, karg1)
class C(A, B):
# extend and override class A
some_method(arg1, arg2, karg1=None):
do_other_stuff(arg1, arg2, karg1)
super(C, self).some_method(arg1, arg2, karg1)
This way, when some_method will be call from C or C childrens, all theses calls will be made in this order:
C.some_method
A.some_method
B.some_method
Check The wonders of cooperative inheritance for more info on the subject.
This looks so painful, you have to consider composition/delegation instead of contorting inheritance this way. What do you think of something like this?
class A(object):
def from_B(self):
return False
class B(object):
def from_B(self):
return True
class C(object):
pass
class PolyClass(object):
def __init__(self, *args):
self.delegates = [c() for c in args[::-1]]
def __getattr__(self, attr):
for d in self.delegates:
if hasattr(d, attr):
return getattr(d,attr)
raise AttributeError(attr + "? what the heck is that?")
def __repr__(self):
return "<instance of (%s)>" % ','.join(d.__class__.__name__
for d in self.delegates[::-1])
pc1 = PolyClass(A,B)
pc2 = PolyClass(A,C)
pc3 = PolyClass(B,C)
for p in (pc1,pc2,pc3):
print p, p.from_B()
print pc1.from_C()
Prints:
<instance of (A,B)> True
<instance of (A,C)> False
<instance of (B,C)> True
Traceback (most recent call last):
File "varying_delegation.py", line 33, in <module>
print pc1.from_C()
File "varying_delegation.py", line 21, in __getattr__
raise AttributeError(attr + "? what the heck is that?")
AttributeError: from_C? what the heck is that?
EDIT:
Here's how to take the not-in-your-control classes A and B, and create custom C classes that look like they extend either an A or a B:
# Django admin classes
class A(object):
def from_B(self):
return False
class B(A):
def from_B(self):
return True
# Your own class, which might get created with an A or B instance
class C(object):
def __init__(self, obj):
self.obj = obj
def __getattr__(self, attr):
return getattr(self.obj, attr)
# these are instantiated some way, not in your control
a,b = A(), B()
# now create different C's
c1 = C(a)
c2 = C(b)
print c1.from_B()
print c2.from_B()
prints:
False
True
And to create your subclasses D and E, create an interim subclass of C (I called it SubC cause I lack imagination), which will auto-init the C superclass with a specific global variable, either a or b.
# a subclass of C for subclasses pre-wired to delegate to a specific
# global object
class SubC(C):
c_init_obj = None
def __init__(self):
super(SubC,self).__init__(self.c_init_obj)
class D(SubC): pass
class E(SubC): pass
# assign globals to C subclasses so they build with the correct contained
# global object
D.c_init_obj = a
E.c_init_obj = b
d = D()
e = E()
print d.from_B()
print e.from_B()
Again, prints:
False
True

Categories