[Python-ideas] @partials = decorators

Ron Adam ron3200 at gmail.com
Mon Feb 10 00:21:08 CET 2014


On 02/08/2014 04:46 PM, Ian Cordasco wrote:
 > I experimented with something similar over at
 > https://github.com/sigmavirus24/curryer but never released it. It's
 > fairly well tested but I'm not sure I'd use it for anything other
 > than a toy.


Thanks I take a look, probably later this week, and see what is different.

Today I made a nicer version. This fits both uses as decorator helpers and
as a way to capture arguments for nested functions.What it's does is
factor out nesting used to capture the arguments. And has some useful
properties to help it work in more situations than the functools partial
function.


It also expresses lambda expressions surprisingly well! ;-)

@P
def I(x):
""" I := λx.x (Identity) """
return x
@P
def K(x, y):
""" K := λx.λy.x """
return x
@P
def S(x, y, z):
""" S := λx.λy.λz.x z (y z) """
return x(z)(y(z))

# Use "_" as the lambda symbol.
_ = P # Allows _(S, K, K, Z) in place of S(K)(K)(Z) or S(K, K, Z)

assert _(I, 'A'), 'A'
assert _(K, 'A', 'B'), 'B'
assert _(S, K, K, 'C'), 'C' # Yes, This really works! :-)


When I get back from a short 3 day trip, I'm going to test
how it works in the library by replacing occurrences of "\n at ..."
with "\n at P(...)" and see what breaks. Should be interesting. I expect
it to get hung up on optional args and keywords in places. But possibly
not, as they will be included at the same time the partial is complete
in most, if not all cases.

One thing that bother me is that, catching TypeError is way to general.
It would be very nice if "wrong number of args" raised a subclass of
TypeError so we can test for just that type of error.

Cheers,
Ron


(Tests not included to keep it short.)
------------------------------------------------------
"""
### NOTE: NOT FULLY TESTED!!! Especially with keywords.

A PARTIAL PROTOCOL:

Takes partial number of args and applies them to
a callable. Calls the callable when all it's
functions are present, else returns an updated
partial object.

THE RULES:

* A partial is equivalent to the sum of it's partials.
P(f, a, b, c) == P(f)(a)(b)(c)

* A complete partial is equivalent to the function f
When all the parts are provided.
P(f, a, b, c) == f(a, b, c)

* If f takes no arguments. (same rule)
P(f) == f()

* And empty partial evaluates to a partial.
P() == P

* If f requires at least one (not provided) argument.
P(f)() == P(f)
P()(f) == P(f)


ERROR CASES:

P(1)() # P(1) --> 1, 1 is not a callable.

(* probably more)


CONTROLS:

It's possible to add controls for both the number of args,
and number of Partial calls.

Author: Ronald Adam (ron3200 at gmail.com)

"""

class CollectMore:
""" Collect more args and keywords. """
def __init__(self, f, *args, **kwds):
self.f = f
self.args = list(args)
self.kwds = kwds
def __call__(self, *args, **kwds):
args = self.args + list(args)
self.kwds.update(kwds)
return self.f(*args, **self.kwds)

def P(*args, **kwds):
""" P - Partial function """
if len(args) == len(kwds) == 0:
return P
if len(args) > 0:
f = args[0]
if callable(f):
a = args[1:]
try:
# No required args needed.
# or all required args present.
return f(*a, **kwds)
except TypeError:
# Better way to do this?
pass
elif len(args) == 1 and len(kwds) == 0:
return f
return CollectMore(P, *args, **kwds)


# The implementation above also makes the functions that
# use partial decorators become partials too. To avoid
# that you can use an N-partial (vs impartial) partial
# decorator.

@P
def NP(n, f, *args, **kwds):
""" NP - N-Partial function

Example:
# Pass only 3 arguments for foo.
@NP(3)
def foo(*args):
return args
"""
print("NP", n, f, *args, **kwds)
if len(args) == 0:
raise TypeError # Ready for rest, Partial catches this.
elif len(args) < n:
raise ValueError("%s is partial to %s values" % (f.__name__, n))
elif len(args) > n:
raise ValueError("%s is partial to %s values" % (f.__name__, n))
else:
return f(*args, **kwds)


# TODO: A partial depth control function.




More information about the Python-ideas mailing list