[Python-Dev] PEP 309

Nick Coghlan 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
> time.

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 
pretty well.

> * 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.

Regards,
Nick.

-- 
Nick Coghlan   |   ncoghlan at email.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.skystorm.net


More information about the Python-Dev mailing list