[Python-Dev] PEP 309
ncoghlan at iinet.net.au
Sun Feb 27 04:58:50 CET 2005
Raymond Hettinger wrote:
> * The PFA implementation proposed for Py2.4 ran slower than an
> equivalent closure. If the latest implementation offers better
> performance, then that may be a reason for having it around.
Not having done the timing, I'll defer to Paul and yourself here. However, one
of the proposed enhancements is to automatically flatten out nested partial
calls - this won't speed up the basic cases, but will allow incremental
construction in two or more stages without a speed loss at the final call.
> flags rather than the first argument of a binary operator. Still, I
> found closures to be more flexible in that they could handle any
> argument pattern and could freeze more than one variable or keyword at a
I'm not sure what this one is about - the PEP 309 implementation allows a single
partial call to freeze an arbitrary number of positional arguments (starting
from the left), and an arbitrary number of keyword arguments at any position.
(This is why the name was changed from curry to partial - it was general purpose
partial function application, rather than left currying)
> * The instance method limitation never came up for me. However, it
> bites to have a tool working in a way that doesn't match your mental
> model. We have to document the limitations, keep them in mind while
> programming, and hope to remember them as possible causes if bugs ever
> arise. It would be great if these limitations could get ironed out.
The 'best' idea I've come up with so far is to make partial a class factory
instead of a straight class, taking an argument that states how many positional
arguments to prepend at call time. A negative value would result in the addition
of (len(callargs)+1) to the position at call time.
Then, partial() would return a partial application class which appended all
positional arguments at call time, partial(-1) a class which prepended all
positional arguments. partial(1) would be the equivalent of partialmethod, with
the first argument prepended, and the rest appended.
In the general case, partial(n)(fn, *args1)(*args2) would give a call that
looked like fn(args2[:n] + args1 + args2[n:]) for positive n, and
fn(args2[:len(args2)+n+1] + args1 + args2[len(args2)+n+1:]) for negative n. n==0
and n==-1 being obvious candidates for tailored implementations that avoided the
unneeded slicing. The presence of keyword arguments at any point wouldn't affect
the positional arguments.
With this approach, it may be necessary to ditch the flattening of nested
partials in the general case. For instance, partial(partial(-1)(fn, c), a)(b)
should give an ultimate call that looks like fn(a, b, c). Simple cases where the
nested partial application has the same number of prepended arguments as the
containing partial application should still permit flattening, though.
> * Using the word "partial" instead of "lambda" traded one bit of
> unreadability for another.
The class does do partial function application though - I thought the name fit
> * It is not clear that the proposed implementation achieves one of the
> principal benefits laid out in the PEP: "I agree that lambda is usually
> good enough, just not always. And I want the possibility of useful
> introspection and subclassing."
I think it succeeds on the introspection part, since the flattening of nested
partials relies on the introspection abilities. Not so much on the subclassing -
partialmethod wasn't able to reuse too much functionality from partial.
> If we get a better implementation, it would be nice if the PEP were
> updated with better examples. The TkInter example is weak because we
> often want to set multiple defaults at the same time (foreground,
> background, textsize, etc) and often those values are config options
> rather than hardwired constants.
Hmm - the PEP may give a misleading impression of what the current
implementation can and can't do. It's already significantly more flexible than
what you mention here. For instance, most of the examples you give below could
be done using keyword arguments.
That's likely to be rather slow though, since you end up manipulating
dictionaries rather than tuples, so I won't pursue that aspect. Instead, I'm
curious how many of them could be implemented using positional arguments and the
class factory approach described above:
cmp(x,y) # partial()(cmp, refobject)
divmod(x,y) # partial(-1)(y)
filter(p,s) # partial()(filter, p)
getattr(o,n,d) # partial(-1)(getattr, n, d)
hasattr(o,n) # partial(-1)(hasttr, n)
int(x,b) # partial(-1)(int, b)
isinstance(o,c) # partial(-1)(isinstance, c)
issubclass(a,b) # partial(-1)(issubclass, b)
iter(o,s) # partial(-1)(iter, s)
long(x,b) # partial(-1)(long, b)
map(f,s) # partial()(map, f)
pow(x,y,z) # partial(1)(pow, y) OR partial(-1)(pow, z)
# OR partial(-1)(pow, y, z)
range([a],b,[c])# partial(-1)(range, c) (Default step other than 1)
# Always need to specify start, though
reduce(f,s,[i]) # partial()(f
round(x, n) # we would want a right curry.
setattr(o,n,v) # partial(1)(setattr, n)
Essentially, partial() gives left curry type behaviour, partial(-1) gives right
curry behaviour, and partial(n) let's you default an argument in the middle. For
positive n, the number is the index of the first argument locked, for negative n
it is the index of the last argument that is locked.
An argument could be made for providing the names 'leftpartial' (fixing
left-hand arguments) and 'rightpartial' (fixing right-hand arguments) as aliases
for partial() and partial(-1) respectively.
Nick Coghlan | ncoghlan at email.com | Brisbane, Australia
More information about the Python-Dev