Thoughts on cooperative methods
Michele Simionato
mis6 at pitt.edu
Mon Jun 9 15:04:02 EDT 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