Curried class methods?

Scott Lamb srlamb at gmail.com
Wed Aug 16 21:53:36 EDT 2006


I'm trying to dynamically generate class methods which have access to
some state passed in at creation time. (Basically as a workaround to
twisted's trial not having a way to dynamically add stuff. Plain
unittest seems to have TestSuite, but the trial runner doesn't know
about it.)

My first attempt might better illustrate this -

    class Foo:
        def generalized(self, ctx):
            print 'my ctx is %r' % ctx

    for i in ['a','b','c']:
        setattr(Foo, i, lambda self: self.generalized(i))

    foo = Foo()
    foo.a()
    foo.b()
    foo.c()

but this prints "my ctx is c" three times; I'd hoped for a, b, c, of
course. After reading
<http://mail.python.org/pipermail/python-list/2004-July/229478.html>, I
think I understand why this is - "i" doesn't actually get added to each
new function's context; they just reference the global one. Even if I
do this:

    def builder():
        for i in ['a','b','c']:
            setattr(Foo, i, lambda self: self.generalized(i))
    builder()

they'll just keep a reference to the context that was made on entry to
builder() and share it, so the modifications builder() makes to i are
reflected in all three functions.

Okay, take two. I tried this:

    try:
        from functional import partial
    except ImportError:
        ...partial pasted from PEP 309...

    for i in ['a','b','c']:
        setattr(Foo, i, partial(Foo.generalized, ctx=i))

but when I try to call foo.a(), I get this error:

    Traceback (most recent call last):
      File "./foo.py", line 35, in ?
        foo.a()
      File "./foo.py", line 25, in __call__
        return self.fn(*(self.args + args), **d)
    TypeError: unbound method generalized() must be called with Foo
instance as first argument (got nothing instead)

If I add a debug print to partial.__call__,

            print 'partial.__call__(args=%r, kw=%r, self.kw=%r)' \
                  % (args, kw, self.kw)

I see:

    partial.__call__(args=(), kw={}, self.kw={'ctx': 'a'})

I'd first expected foo.a() to be equivalent to Foo.a(self), but instead
it's like Foo.a(). There must be magic that does the equivalent of

    class Foo:
        def __init__(self):
            a = partial(a, self)

for real Python functions and not for my partial object.

With this __init__ magic, I guess I have something that works. I have
to apply the partial twice, though - if I do everything in the
__init__, my new functions won't exist by the time trial's
introspection kicks in, so they'll never get called. My ugly hack has
gotten even uglier.

Does anyone know of a better way to do this?

Thanks,
Scott




More information about the Python-list mailing list