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(ClassA,SubClass).__new__(SubClass,*arg_l) # ,**arg_d) # IGNORE init kwabc
# ./new_init.py:99: DeprecationWarning: object.__new__() takes no parameters
    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(ClassA,self).__init__(*arg_l) # ,**arg_d) # IGNORE new kwABC
# ./new_init.py:99: DeprecationWarning: object.__init__() takes no parameters
    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(ClassAB,SubClass).__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(ClassAB,self).__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(ClassAC,self).__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(ClassABC,self).__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') kwa=kwa,kwA=kwA,kwabc=kwabc,kwAC=kwAC,kwab=kwab,kwac=kwac,kwAB=kwAB
  BEGIN ('new.ClassAB', <class '__main__.ClassABC'>, 'AB', 'AC', 'A') kwa=kwa,kwA=kwA,kwabc=kwabc,kwac=kwac,kwab=kwab,kwAC=kwAC
    BEGIN ('new.ClassAC', <class '__main__.ClassABC'>, 'AC', 'A') kwa=kwa,kwA=kwA,kwabc=kwabc,kwac=kwac,kwab=kwab
      BEGIN ('new.ClassA', <class '__main__.ClassABC'>, 'A') kwa=kwa,kwab=kwab,kwac=kwac,kwabc=kwabc
      END ('new.ClassA INSTANCE!', <__main__.ClassABC object at 0x7f631d0bd4d0>)
    END ('new.ClassAC INSTANCE!', <__main__.ClassABC object at 0x7f631d0bd4d0>)
  END ('new.ClassAB INSTANCE!', <__main__.ClassABC object at 0x7f631d0bd4d0>)
END ('new.ClassABC INSTANCE!', <__main__.ClassABC object at 0x7f631d0bd4d0>)
BEGIN ('init.ClassABC', <__main__.ClassABC object at 0x7f631d0bd4d0>, 'ABC', 'AB', 'AC', 'A') kwa=kwa,kwA=kwA,kwABC=kwABC,kwAC=kwAC,kwab=kwab,kwac=kwac,kwAB=kwAB
  BEGIN ('init.ClassAB', <__main__.ClassABC object at 0x7f631d0bd4d0>, 'AB', 'AC', 'A') kwa=kwa,kwA=kwA,kwABC=kwABC,kwac=kwac,kwAB=kwAB,kwAC=kwAC
    BEGIN ('init.ClassAC', <__main__.ClassABC object at 0x7f631d0bd4d0>, 'AC', 'A') kwa=kwa,kwA=kwA,kwABC=kwABC,kwAC=kwAC,kwAB=kwAB
      BEGIN ('init.ClassA', <__main__.ClassABC object at 0x7f631d0bd4d0>, 'A') kwA=kwA,kwAB=kwAB,kwAC=kwAC,kwABC=kwABC
      END ('init.ClassA', None)
    END ('init.ClassAC', None)
  END ('init.ClassAB', None)
END ('init.ClassABC', None)
<__main__.ClassABC object at 0x7f631d0bd4d0>

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.