implicit, may-be-safe super.__init__ call on derived class instantiation: a starting point

What do I really understand by inheritance? I mean, what is to be inherited?
I would say it is behavior ... and that would be enough. It's a sane way to propagate knowledge. You don't want to inherit data from your ancestors other way than compiled as carefully selected behaviors that will eventually help you interpret a live information stream and that you can  upgrade to something well suited for your existence.



The case when you need to pass datum attributes to your deriveds should be exceptional and when it happens it should be object-based inheritance, not class-based. That is, the datum is inherited (or not) at object instantiation time. We can think of human eye color. An offspring can or - god forbid,   cannot  have eyes but this is to be computed at instantiation time so basically there's no need to define (yet) a datum eye_color attribute for the class.
We can spot a small problem here as the eyeless offspring will have a different set of  behaviors since its perception mechanisms are altered.
But it is just a small problem, not an error. Python comes to the rescue here (same as mother nature does in our case) by giving you - the god of your world -  a chance to alter the behavior at instantiation time, based on given input data and creation policies. Here, meta classes come to the rescue.


My needs of auto-calling super.__init__ when instantiating a derived class were in fact the result of a pythonic class design. Design that, I must agree, it is closer to my soul compared to C++, for example although at a first glance it  just looked like another bad sketch.


What i need from my base class design is to allow any deriveds to inherit two  instance-based datum attributes at derived's instantiation time. I have no other needs for modifying the behavior of the derived based on the respective properties or to use policy based creation procedures so this is a simple case.


First shot is to use super.__init__(). Well, don't do that. First, that will force the developer to remember that he must call super.__init__() every time he derives from the base in order to have the two properties available in its objects. Second, super.* is BAD. It is bad at least for the case when you enjoy your multiple inheritance, diamond-star or pentagram designs.


Luckly we have the metaclasses. And better, deriveds are going to be bound to the base class metaclass definition. When you define the metaclass for the base class that will be available and used for derived classes instantiation as a creation policy.  So, what we need to do is to define a new creation policy that will transparently call base class __init__  just before the derived __init__ is called. Why? our base class attributes are created at object instantiation time. Basically those don't exist when the inheritance is resolved.




In our case, the base class is called rpso. The object instantiation policy is defined in rpso_meta class, which is ... a metaclass. We need to bind the policy to the base class and that is done by defining the __metaclass__ class attribute: rpso.__metaclass__ = rpso_meta.


Now, any rpso-derived object will inherit the base rpso instance datum attributes when is instantiated.






class rpso_meta(type):
        def __new__(mcls,name,bases,dict):
                rpso_base_class = None
                for b in bases:
                        if b.__name__ == 'rpso':
                                rpso_base_class = b
                                break
                instance = type.__new__(mcls,name,bases,dict)
                if rpso_base_class is None: return instance
                #print "altering RPS object"
                def alter_init(oldinit):
                        def new_ini(theObject,*args,**kw):
                                #super(theObject.__class__,theObject).__init__()
                                rpso_base_class.__init__(theObject,*args,**kw)
                                oldinit(theObject,*args,**kw)
                        return new_ini
                instance.__init__ = alter_init(instance.__init__)
                return instance

class rpso(object):
        __metaclass__=rpso_meta
        def __init__(self):
                self.source = None
                self.changed = None


class route(rpso):
  '''
  now any route objects will have source and changed instance attributes
  when created
 '''
  pass


No comments:

Post a Comment