Python: how to call multiple super __new__ with multiple inheritance? - python

I have 2 classes: A and B, both of them instantiated by __new__ with different set of arguments (like a for A, and foo, bar for B). Now I want to implement class C, inherited from A and B, and instantiate it with 3 args: a, foo, bar, passing correspondent arguments to super classes __new__, but things goes wrong from here.
If we have no arguments I just call super(C, cls).__new__() and object of class C successfully created (it calls both A.__new__() and B.__new__() and combines it somehow). But how to do it 'by hands'? So I want to pass a to A.__new__, foo, bar to B.__new__ and combine somehow returned instances (is this right way to get object of class C at the end?).
Anyway I can't do both.
Fist - calling A.__new__ raises incorrect number of arguments exception in o = super(A, cls).__new__(cls) in A.__new__() (but A can be instantiated as standalone)
Second - I have no idea how to combine even successfully instantiated object of classes A and B into object of class C.
So could please someone to explain what is going on here?
class A(object):
def __new__(cls, a):
o = super(A, cls).__new__(cls)
setattr(o, 'a', a)
return o
class B(object):
def __new__(cls, foo, bar):
o = super(B, cls).__new__(cls)
setattr(o, 'foo', foo)
setattr(o, 'bar', bar)
return o
print A(1) # ok, <__main__.A object at 0x00000000022F1630>
print B(2,3) # ok, <__main__.B object at 0x00000000022F1630>
class C(A,B):
def __new__(cls, a, foo, bar):
o1 = A.__new__(cls, a) #fail - exception while calling super.new in A
o2 = B.__new__(cls, foo, bar) #fail - exception while calling super.new in A
# return # What? How to combine o1 o2 even if they are created succesfuly?
# # return super(C, cls).__new__(cls, ?????)
print C(1,2,3)

The method __new__ is what creates your instance, you should not call super(...).__new__ multiple times as it would create multiple instances.
What you want to do it use __init__ which initializes your already created instance.
class A(object):
def __init__(self, a):
self.a = a
class B(object):
def __init__(self, foo, bar):
self.foo = foo
self.bar = bar
class C(A, B):
def __init__(self, a, foo, bar):
A.__init__(self, a)
B.__init__(self, foo, bar)
In particular, I want to point out that it is not true that on multiple inheritance Python will call both A.__new__ andB.__new__ and "combine somehow". Have a look at this code
class A(object):
def __new__(*args):
print('A.__new__ was called')
return type(*args) # This is what ultimately creates every object in Python
class B(object):
def __new__(*args):
print('B.__new__ was called')
return type(*args)
# As expected the following is printed when instances are created
a = A() # Prints 'A.__new__ was called'
b = B() # Prints 'B.__new__ was called'
class C(A, B):
pass
c = C() # Prints 'A.__new__ was called'
So we observe that B.__new__ was never called. On multiple inheritance, Python will inherit the method from the left-most class that has this method. In this case, C inherited A.__new__.

Related

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

Upcast instance to parent class in python

(How) Is it possible in Python to treat an instance of class B exactly as an instance of class A, where A is a parent of B (like up-casting in compiled languages)?
Say we have the following:
class A:
def __init__(self, prop=None):
self.prop = prop
def f(self):
return 'A.f'
def g(self):
return 'A.g', self.f()
class B(A):
def f(self):
return 'B.f'
def g(self):
return 'B.g', self.f()
Calling A().g() produces ('A.g', 'A.f'), whereas B().g() produces ('B.g', 'B.f'). Calling super(B, B()).g() produces ('A.g', 'B.f'), which is different from compiled languages, so I cannot use super. Instead, I need a function that changes the type from which an instance's methods are resolved, but preserves (a reference to) the original state. In short, I'm looking for a way to do this:
b = B(object())
a = upcast(b, A)
a.prop = object()
assert isinstance(a, A)
assert a.g() == ('A.g', 'A.f')
assert a.prop is b.prop
The closest I could get is
a = copy.copy(b)
a.__class__ = A
a.__dict__ = b.__dict__
(assuming A/B are "nice" "heap" classes), but this makes unnecessary copies of all objects in the __dict__ before I discard them. Is there a better way to do this?

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.

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

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