Re: Small change to utils.functional.curry

2006-09-06 Thread Michael Spencer

Adrian Holovaty wrote:
> On 9/6/06, Martin <[EMAIL PROTECTED]> wrote:
>> def curry(fct, *args, **kwargs):
>> def _curried(*moreargs, **morekwargs):
>> return fct (* (args+moreargs), **dict(kwargs, ** morekwargs))
>> return _curried
>>
>> It's just a performance issue (saving a few list operations) andbecause
>> I have to use CGI I care about performance at bit more than others ...
> 
> Thanks for the great catch and fix, Martin! I've checked in your
> change in changeset 3733. Please do let us know if you find any other
> places we could optimize.
> 
> Adrian
> 
Martin's version is indeed faster, but has a 'gotcha' in that it precludes 
passing a keyword arguments named 'fct' to a curried function...

 >>> def curry1(*args, **kwargs):
... def _curried(*moreargs, **morekwargs):
... return args[0](*(args[1:]+moreargs), **dict(kwargs.items() +
... morekwargs.items()))
... return _curried
...
...
 >>> def curry2(fct, *args, **kwargs):
... def _curried(*moreargs, **morekwargs):
... return fct (* (args+moreargs), **dict(kwargs, ** morekwargs))
... return _curried
...

Check the speed up...

 >>> def adder(a,b,c,d):
... return a+b+c+d
...
 >>> adder_cur1 = curry1(adder, 1, 2)
 >>> adder_cur2 = curry2(adder, 1, 2)
 >>> shell.timefunc(adder_cur1, 3, 4)
'_curried(...)  37276 iterations, 13.41usec per call'

 >>> shell.timefunc(adder_cur2, 3, 4)
'_curried(...)  54986 iterations, 9.09usec per call'

which is about 30% faster in this case


However...

 >>> def adder2(a,b,c,d, fct=sum):
... return fct([a,b,c,d])
...
 >>> import sys
 >>> p1 = curry1(1,2, fct="".join)
 >>> p2 = curry2(1,2, fct="".join)
...
Traceback (most recent call last):
   File "", line 1, in ?
TypeError: curry2() got multiple values for keyword argument 'fct'
 >>>

Oops.

Better to rename fct to something less likely to clash, such as _curried_fct

If you're really hunting for speed, there is a significant boost available in 
the common special case of a python function curried with one positional 
argument.  In this case, you can [ab]use the method binding machinery, e.g.:

def curry3(_curried_fct, *args, **kwargs):
 if len(args) == 1:
 # Special case where we try to abuse the descriptor
 try:
 return _curried_fct.__get__(args[0])
 except AttributeError:
 # built-ins fail - handle them in the normal way
 pass

 def _curried(*moreargs, **morekwargs):
 return _curried_fct (*(args+moreargs), **dict(kwargs, ** morekwargs))

 return _curried


This is about twice as fast as curry2 for the case of a python function curried 
with one argument. It also offers better introspection than either curry1 or 
curry2, since the original signature is preserved.  It's a bigger change than 
the curry2 though, since it changes the type of the curry from function to 
bound 
method.

Michael


--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-developers
-~--~~~~--~~--~--~---



Re: Small change to utils.functional.curry

2006-09-07 Thread Michael Spencer

Marc D.M. wrote:
> On Thu, 2006-09-07 at 13:28 -0500, Adrian Holovaty wrote:
>> On 9/7/06, Michael Spencer <[EMAIL PROTECTED]> wrote:

>>
>>> If you're really hunting for speed, there is a significant boost available 
>>> in
>>> the common special case of a python function curried with one positional
>>> argument.  In this case, you can [ab]use the method binding machinery, e.g.:
>>> [...]
>>> This is about twice as fast as curry2 for the case of a python function 
>>> curried
>>> with one argument. It also offers better introspection than either curry1 or
>>> curry2, since the original signature is preserved.  It's a bigger change 
>>> than
>>> the curry2 though, since it changes the type of the curry from function to 
>>> bound
>>> method.
>> I was with you until "it changes the type of the curry from function
>> to bound method." How does it do that?
>>
>> Adrian
> 
> I think what's going on is that in the try : except clause, the return
> value is the actual bound method, retrieved using the __get__
> magic-method.
> 
Yep.

> And it doesn't work very well that way either. I've tried Michael's
> method and the whole house comes tumbling down. See traceback below when
> trying to load a flatpage.
> 
[snip...house falling down]

I should have included a guard to ensure that the shortcut applies only to 
functions, not bound methods.  Something like the following...

import types
def curry3(_curried_fct, *args, **kwargs):
 if len(args) == 1 and type(_curried_fct) is types.FunctionType:
 # Special case where we try to abuse the descriptor
 try:
 return _curried_fct.__get__(args[0])
 except AttributeError:
 # built-ins fail - handle them in the normal way
 pass

 def _curried(*moreargs, **morekwargs):
 return _curried_fct (*(args+moreargs), **dict(kwargs, ** morekwargs))

 return _curried

This is still just a sketch, but my own django app runs with curry defined this 
way.

Michael


--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-developers
-~--~~~~--~~--~--~---



Re: Small change to utils.functional.curry

2006-09-07 Thread Michael Spencer

Marc D.M. wrote:
> On Wed, 2006-09-06 at 22:49 -0700, Michael Spencer wrote:
>> If you're really hunting for speed, there is a significant boost available 
>> in 
>> the common special case of a python function curried with one positional 
>> argument.  In this case, you can [ab]use the method binding machinery...

> > ...it changes the type of the curry from function to bound
>> method.


>>
> I like this idea, but I'm curious about that last sentence. Is this
> necessarily a bad thing? Or maybe I should ask, why does it matter,
> function vs bound method. Aren't they both callable?
> 
> Or am I missing something?
> 

Yes, they're both callable.  The differences are likely to matter in edge cases 
- but you found one already: the behavior of the __get__ method.

Michael


--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-developers
-~--~~~~--~~--~--~---