Friday, October 30, 2015

Sometimes Python's multiple inheritance has me scratching my head...

I was trying to figure out and demonstrate python's Member Resolution Order (python2.7)... In particular, in the following, I suggest you pay attention to the member functions __new__ and __init__. Bear in mind that the following code (save for the calls to functions 'begin' & 'end') use only the minimum of code infrastructure to create subclass Class ABC from ClassAB and ClassAC (of ClassA) using multiple inheritance. If you wondered why this was so hard, then read on...
#!/usr/bin/env python
# -*- coding: utf-8 -*-
depth=0
def begin(*arg_l,**arg_d):
global depth
print(" "*depth+"BEGIN",arg_l,",".join("%s=%s"%item for item in arg_d.items()))
depth+=1
def end(*arg_l):
global depth
depth-=1
print(" "*depth+"END",arg_l)
class ClassA(object):
def __new__(SubClass,a,kwA="kwA",*arg_l,**arg_d):
begin("new.ClassA",SubClass,a,*arg_l,**arg_d)
out_instance=super().__new__(SubClass,*arg_l) # ,**arg_d) # IGNORE init kwabc
end("new.ClassA INSTANCE!",out_instance)
return out_instance
def __init__(self,a,kwa="kwa",*arg_l,**arg_d):
begin("init.ClassA",self,a,*arg_l,**arg_d)
none=super().__init__(*arg_l) # ,**arg_d) # IGNORE new kwABC
end("init.ClassA",none)
return none # Always None
class ClassAB(ClassA):
def __new__(SubClass,ab,ac,a,kwAB="kwAB",*arg_l,**arg_d):
begin("new.ClassAB",SubClass,ab,ac,a,*arg_l,**arg_d)
out_instance=super().__new__(SubClass,ac,a,*arg_l,**arg_d)
end("new.ClassAB INSTANCE!",out_instance)
return out_instance
def __init__(self,ab,ac,a,kwab="kwab",*arg_l,**arg_d):
begin("init.ClassAB",self,ab,ac,a,*arg_l,**arg_d)
none=super().__init__(ac,a,*arg_l,**arg_d)
end("init.ClassAB",none)
return none # Always None
class ClassAC(ClassA):
def __new__(SubClass,ac,a,kwAC="kwAC",*arg_l,**arg_d):
begin("new.ClassAC",SubClass,ac,a,*arg_l,**arg_d)
out_instance=super(ClassAC,SubClass).__new__(SubClass,a,*arg_l,**arg_d)
end("new.ClassAC INSTANCE!",out_instance)
return out_instance
def __init__(self,ac,a,kwac="kwac",*arg_l,**arg_d):
begin("init.ClassAC",self,ac,a,*arg_l,**arg_d)
none=super().__init__(a,*arg_l,**arg_d)
end("init.ClassAC",none)
return none # Always None
class ClassABC(ClassAB,ClassAC):
def __new__(SubClass,abc,ab,ac,a,kwABC="kwABC",*arg_l,**arg_d):
begin("new.ClassABC",SubClass,abc,ab,ac,a,*arg_l,**arg_d)
out_instance=super(ClassABC,SubClass).__new__(SubClass,ab,ac,a,*arg_l,**arg_d)
end("new.ClassABC INSTANCE!",out_instance)
return out_instance
def __init__(self,abc,ab,ac,a,kwabc="kwabc",*arg_l,**arg_d):
begin("init.ClassABC",self,abc,ab,ac,a,*arg_l,**arg_d)
none=super().__init__(ab,ac,a,*arg_l,**arg_d)
end("init.ClassABC",none)
return none # Always None
print(ClassABC(
"ABC","AB","AC","A",
kwABC="kwABC",kwAB="kwAB",kwAC="kwAC",kwA="kwA",
kwabc="kwabc",kwab="kwab",kwac="kwac",kwa="kwa"
))

Output:

 
BEGIN ('new.ClassABC', <class '__main__.ClassABC'>, 'ABC', 'AB', 'AC', 'A') kwAB=kwAB,kwAC=kwAC,kwA=kwA,kwabc=kwabc,kwab=kwab,kwac=kwac,kwa=kwa
BEGIN ('new.ClassAB', <class '__main__.ClassABC'>, 'AB', 'AC', 'A') kwAC=kwAC,kwA=kwA,kwabc=kwabc,kwab=kwab,kwac=kwac,kwa=kwa
BEGIN ('new.ClassAC', <class '__main__.ClassABC'>, 'AC', 'A') kwA=kwA,kwabc=kwabc,kwab=kwab,kwac=kwac,kwa=kwa
BEGIN ('new.ClassA', <class '__main__.ClassABC'>, 'A') kwabc=kwabc,kwab=kwab,kwac=kwac,kwa=kwa
END ('new.ClassA INSTANCE!', <__main__.ClassABC object at 0x7f8a829da190>)
END ('new.ClassAC INSTANCE!', <__main__.ClassABC object at 0x7f8a829da190>)
END ('new.ClassAB INSTANCE!', <__main__.ClassABC object at 0x7f8a829da190>)
END ('new.ClassABC INSTANCE!', <__main__.ClassABC object at 0x7f8a829da190>)
BEGIN ('init.ClassABC', <__main__.ClassABC object at 0x7f8a829da190>, 'ABC', 'AB', 'AC', 'A') kwABC=kwABC,kwAB=kwAB,kwAC=kwAC,kwA=kwA,kwab=kwab,kwac=kwac,kwa=kwa
BEGIN ('init.ClassAB', <__main__.ClassABC object at 0x7f8a829da190>, 'AB', 'AC', 'A') kwABC=kwABC,kwAB=kwAB,kwAC=kwAC,kwA=kwA,kwac=kwac,kwa=kwa
BEGIN ('init.ClassAC', <__main__.ClassABC object at 0x7f8a829da190>, 'AC', 'A') kwABC=kwABC,kwAB=kwAB,kwAC=kwAC,kwA=kwA,kwa=kwa
BEGIN ('init.ClassA', <__main__.ClassABC object at 0x7f8a829da190>, 'A') kwABC=kwABC,kwAB=kwAB,kwAC=kwAC,kwA=kwA
END ('init.ClassA', None)
END ('init.ClassAC', None)
END ('init.ClassAB', None)
END ('init.ClassABC', None)
<__main__.ClassABC object at 0x7f8a829da190>

Updated for Py3's super()

eg. none=super().__init__(*arg_l)

Conclusion:

Similar to a MS-Word Template document, Python classes appear temptingly easy, that is until you need to do multiple inheritance, and then there is copious amounts of syntactic-salt & syntactic-vinegar that the coder needs carefully balance to get the right taste. 


Note that arg_l is virtually unusable because of a clash with keyword arguments. And positional arguments need to be carefully counted and consumed, otherwise each class with "TypeError: __new__() got multiple values for keyword argument 'kwABC'". Even restricting yourself to keyword arguments only is problematic, in particular __new__ and __init__ of the same should "consume" exactly the same keyword argument, otherwise you will get the warnings: "DeprecationWarning: object.__init__() takes no parameters" etc.


However... maybe... with an appropriate argument naming convention the probability of a coder putting bullet hole in their own foot would be reduced.
Hints welcomed.

No comments:

Post a Comment