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