Skip to content

Commit

Permalink
More curry changes, including sometimes returning functools.partial:
Browse files Browse the repository at this point in the history
1. Once again, `Curry` is a class and `curry` is a function.
2. `Curry`and `curry` may return a partial instance if there is a single
   argument remaining and no keywords.
3. `curried_object.call` was changed to `curried_object._call`.  As per
   usual convention, `_call` is an implementation detail that may occasionally
   be useful (as it is explicit and faster), but there are no guarantees
   that it will remain in future releases of `toolz`.
  • Loading branch information
eriknw committed Mar 20, 2014
1 parent 9fc06dd commit 2ca1c7c
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 52 deletions.
125 changes: 77 additions & 48 deletions toolz/functoolz/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,53 +105,39 @@ def _num_required_args(func):
return None


class curry(object):
""" Curry a callable function
Enables partial application of arguments through calling a function with an
incomplete set of arguments.
>>> def mul(x, y):
... return x * y
>>> mul = curry(mul)
def _may_have_kwargs(func):
""" Whether a function may have keyword arguments."""
try:
spec = inspect.getargspec(func)
return bool(not spec or spec.keywords or spec.defaults)
except TypeError:
return True

>>> double = mul(2)
>>> double(10)
20

Also supports keyword arguments
def _is_unary(func, spec=None):
""" Whether func is a unary functions (single argument input only)"""
try:
spec = inspect.getargspec(func)
return (spec and spec.varargs is None and spec.keywords is None
and spec.defaults is None and len(spec.args) == 1)
except TypeError:
return False

>>> @curry # Can use curry as a decorator
... def f(x, y, a=10):
... return a * (x + y)

>>> add = f(a=1)
>>> add(2, 3)
5
class Curry(object):
""" A curried function
See Also:
toolz.curried - namespace of curried functions
http://toolz.readthedocs.org/en/latest/curry.html
curry
"""
def __init__(self, func, *args, **kwargs):
if not callable(func):
raise TypeError("Input must be callable")

# curry- or functools.partial-like object? Unpack and merge arguments
if (hasattr(func, 'func') and hasattr(func, 'args')
and hasattr(func, 'keywords')):
_kwargs = {}
if func.keywords:
_kwargs.update(func.keywords)
_kwargs.update(kwargs)
kwargs = _kwargs
args = func.args + args
func = func.func

if kwargs:
self.call = partial(func, *args, **kwargs)
self._call = partial(func, *args, **kwargs)
else:
self.call = partial(func, *args)
self._call = partial(func, *args)
self.__doc__ = func.__doc__
try:
self.func_name = self.func.func_name
Expand All @@ -160,15 +146,15 @@ def __init__(self, func, *args, **kwargs):

@property
def func(self):
return self.call.func
return self._call.func

@property
def args(self):
return self.call.args
return self._call.args

@property
def keywords(self):
return self.call.keywords
return self._call.keywords

def __str__(self):
return str(self.func)
Expand All @@ -178,7 +164,7 @@ def __repr__(self):

def __call__(self, *args, **kwargs):
try:
return self.call(*args, **kwargs)
return self._call(*args, **kwargs)
except TypeError:
required_args = _num_required_args(self.func)

Expand All @@ -187,7 +173,7 @@ def __call__(self, *args, **kwargs):
len(args) + len(self.args) >= required_args):
raise

return curry(self.call, *args, **kwargs)
return curry(self._call, *args, **kwargs)

# pickle protocol because functools.partial objects can't be pickled
def __getstate__(self):
Expand All @@ -198,6 +184,56 @@ def __setstate__(self, state):
self.__init__(func, *args, **(kwargs or {}))


def curry(func, *args, **kwargs):
""" Curry a callable function
Enables partial application of arguments through calling a function with an
incomplete set of arguments.
>>> def mul(x, y):
... return x * y
>>> mul = curry(mul)
>>> double = mul(2)
>>> double(10)
20
Also supports keyword arguments
>>> @curry # Can use curry as a decorator
... def f(x, y, a=10):
... return a * (x + y)
>>> add = f(a=1)
>>> add(2, 3)
5
For performance reasons, ``curry`` or a curried function will return a
``functools.partial`` instance if only one more argument is required
and the function has no keywords.
See Also:
toolz.curried - namespace of curried functions
http://toolz.readthedocs.org/en/latest/curry.html
"""
# curry- or functools.partial-like object? Unpack and merge arguments
if (hasattr(func, 'func') and hasattr(func, 'args')
and hasattr(func, 'keywords')):
_kwargs = {}
if func.keywords:
_kwargs.update(func.keywords)
_kwargs.update(kwargs)
kwargs = _kwargs
args = func.args + args
func = func.func

required_args = _num_required_args(func)
if (required_args is not None and required_args - len(args) == 1
and not _may_have_kwargs(func)):
return partial(func, *args, **kwargs)
return Curry(func, *args, **kwargs)


@curry
def memoize(func, cache=None):
""" Cache a function's result for speedy future evaluation
Expand Down Expand Up @@ -226,15 +262,8 @@ def memoize(func, cache=None):
if cache is None:
cache = {}

try:
spec = inspect.getargspec(func)
may_have_kwargs = bool(not spec or spec.keywords or spec.defaults)
# Is unary function (single arg, no variadic argument or keywords)?
is_unary = (spec and spec.varargs is None and not may_have_kwargs
and len(spec.args) == 1)
except TypeError:
may_have_kwargs = True
is_unary = False
may_have_kwargs = _may_have_kwargs(func)
is_unary = not may_have_kwargs and _is_unary(func)

def memof(*args, **kwargs):
try:
Expand Down
8 changes: 4 additions & 4 deletions toolz/functoolz/tests/test_core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from toolz.functoolz import (thread_first, thread_last, memoize, curry,
compose, pipe, complement, do)
from toolz.functoolz.core import _num_required_args
from toolz.functoolz.core import _num_required_args, Curry
from operator import add, mul
from toolz.utils import raises
from functools import partial
Expand Down Expand Up @@ -218,9 +218,9 @@ def foo(a, b, c=1):

f = curry(foo, 1, c=2)
g = curry(f)
assert isinstance(f, curry)
assert isinstance(g, curry)
assert not isinstance(f.func, curry)
assert isinstance(f, Curry)
assert isinstance(g, Curry)
assert not isinstance(g.func, Curry)
assert not hasattr(g.func, 'func')
# curry makes a new curry object, so everything is distinct but equal
assert f is not g
Expand Down

0 comments on commit 2ca1c7c

Please sign in to comment.