Thoughts on cooperative methods

Michele Simionato mis6 at pitt.edu
Mon Jun 9 21:04:02 CEST 2003


hellen at claggetts.net (Jonathan Claggett) wrote in message news:<dd5454fb.0306071114.40b8ef4c at posting.google.com>...
> I've recently played around with using cooperative methods in python
> via the new super function and I wanted to bounce some of my thoughts
> off the python community as a sanity check. So with no particular
> rhyme and reason:
> 
> 1) When you've got cooperative methods that are 'public' (i.e., called
> from outside of the class), it is generally a good idea to refactor
> your method into two new methods: a private cooperative method and a
> public non-cooperative method. This is because the parameters of
> cooperative methods are more constrained than normal method (since
> they are cooperating). For example:
> 
> class A(object):
>     def __init__(self, arg1):
>         print arg1
> 
> class B(A):
>     def __init__(self, arg1, arg2):
>         super(B, self).__init__(arg1):
>         print arg2
> 
> is refactored into:
> 
> class A(object):
>     def __init__(self, arg1):
>         self.c_init(arg1=arg1)
>     def c_init(**dic):
>         print dic['arg1']
> 
> class B(A):
>     def __init__(self, arg1, arg2):
>         self.c_init(arg1=arg1, arg2=arg2)
>     def c_init(self, **dic):
>         super(B, self).c_init(**dic)
>         print dic['arg2']
> 
> 2) The existing super() is limited in that you have to explicitly call
> it at every level or the chain of cooperation stops. In the code
> above, the method A.c_init can't call super() since there is no c_init
> method explicitly defined above it. This limitation means that it is
> tricky to use cooperative methods with mixins or other classes. for
> example:
> 
> class Mixin(object):
>     def __init__(self **dic):
>         self.c_init(**dic)
>     def c_init(self, **dic):
>         print dic
>         super(Mixin, self).c_init(**dic)
> 
> class C(Mixin, B): pass # works
> class C(B, Mixin): pass # breaks, will never call Mixin.c_init
> m = Mixin(arg1=1, arg2=2) # breaks, super() raises an error
> 
> 3) The existing super() feels awkward to use with all of its
> parameters and I'm hoping to see a proper super keyword in a future
> release of python. It just seems like a hack at this point (not that
> I'm morally opposed to hacks or anything ;-).
> 
> 4) Finally, as an experiment, I've developed an entirely different
> approach to cooperative methods which removes the chaining limitation
> above and has a slightly less awkward syntax. This approach uses
> generators and yes, this is a hack and no, I'm not suggesting its use
> for production work. However, I do think it is a good template for how
> the super keyword might work. For example the above code becomes:
> 
> class A(object):
>     def __init__(self, arg1):
>         cooperate(self.c_init, arg1=arg1)
>     def c_init(self, **dic):
>         print dic['arg1']
> 
> class B(A):
>     def __init__(self, arg1, arg2):
>         cooperate(self.c_init, arg1=arg1, arg2=arg2)
>     def c_init(self, **dic):
>         yield self
>         print dic['arg2']
> 
> class Mixin(object):
>     def __init__(self, **dic):
>         cooperate(self.c_init, **dic)
>     def c_init(self, **dic):
>         print dic
> 
> class C(B, Mixin): pass
> 
> c = C(arg1=1, arg2=2)
> 
> where cooperate() is defined as:
> 
> from __future__ import generators
> import types
> 
> def cooperate(method, *args, **kw):
>     assert type(method) is types.MethodType
> 
>     attr_dict = dict()
>     gen_list = list()
> 
>     for cls in method.im_class.__mro__:
>         attr = getattr(cls, method.im_func.func_name, None)
>         if attr is not None and hash(attr) not in attr_dict:
>             attr_dict[hash(attr)] = None
>             gen = attr(method.im_self, *args, **kw)
>             if type(gen) is types.GeneratorType:
>                 try: gen.next()
>                 except StopIteration: pass
>                 else: gen_list.insert(0, gen)
> 
>     for gen in gen_list:
>         try: gen.next()
>         except StopIteration: pass
> 
> Sorry for the long winded post but I wanted to know what the python
> folk thought about these issues.
> 
> Thanks,
> Jonathan

A simpler alternative is to use metaclasses:

class Cooperative(type):
    """Metaclass implementing cooperative methods. Works
    well for methods returning None, such as __init__"""
    def __init__(cls,name,bases,dic):
        for meth in getattr(cls,'__cooperative__',[]): 
            setattr(cls,meth,cls.coop_method(meth,dic.get(meth)))
    def coop_method(cls,name,method): # method can be None
        """Calls both the superclass method and the class method (if the 
        class has an explicit method). Implemented via a closure"""
        def _(self,*args,**kw):
            getattr(super(cls,self),name)(*args,**kw) # call the supermethod
            if method: method(self,*args,**kw) # call the method
        return _

class A(object):
    __metaclass__=Cooperative
    __cooperative__=['__init__']
    def __init__(self, *args):
        print 'A.__init__',args

class B(A):
    def __init__(self, *args):
        print 'B.__init__',args

class C(B):
    def __init__(self, *args):
        print 'C.__init__',args

C()

# Gives:
#A.__init__ ()
#B.__init__ ()
#C.__init__ ()

                                               Michele




More information about the Python-list mailing list