Thoughts on cooperative methods

Jonathan Claggett hellen at claggetts.net
Sat Jun 7 15:14:36 EDT 2003


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




More information about the Python-list mailing list