Curried class methods?

S

Scott Lamb

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
 
A

Antoon Pardon

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:

Try this instead as the for loop

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

Carl Banks

Scott said:
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.

def build(j):
setattr(Foo, j, lambda self: self.generalized(j))
for i in ["a","b","c"]:
build(i)

Each call of the the build function creates its own cell "j" that the
lambda references.


Carl Banks
 
S

Scott Lamb

Thanks, Antoon and Carl. Just tried your solutions - both work and are
much cleaner than mine.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top