Python用任意数量的变量进行curry

时间:2016-08-06 03:22:47

标签: python python-2.7 currying

我正在尝试使用currying在Python中进行简单的功能添加。我找到了这个咖喱装饰师here

def curry(func):     
    def curried(*args, **kwargs):
        if len(args) + len(kwargs) >= func.__code__.co_argcount:
            return func(*args, **kwargs)
        return (lambda *args2, **kwargs2:
            curried(*(args + args2), **dict(kwargs, **kwargs2)))
    return curried

@curry
def foo(a, b, c):
    return a + b + c

现在这很棒,因为我可以做一些简单的讨论:

>>> foo(1)(2, 3)
6
>>> foo(1)(2)(3)
6

但这仅适用于三个变量。如何编写函数foo以便它可以接受任意数量的变量并且仍然可以调整结果?我尝试过使用* args的简单解决方案,但它没有用。

编辑:我看过答案,但仍然无法弄清楚如何编写一个可以执行如下所示的功能:

>>> foo(1)(2, 3)
6
>>> foo(1)(2)(3)
6
>>> foo(1)(2)
3
>>> foo(1)(2)(3)(4)
10

4 个答案:

答案 0 :(得分:5)

可以说是explicit is better than implicit

from functools import partial

def example(*args):
    print("This is an example function that was passed:", args)

one_bound = partial(example, 1)
two_bound = partial(one_bound, 2)
two_bound(3)
@JohnKugelman用你正在尝试做的事情解释了设计问题 - 对于咖喱功能的调用在"添加更多咖喱论点和#34;之间是模棱两可的。和"调用逻辑"。这个问题在Haskell(概念的来源)中不是问题,因为语言懒惰地评估所有,所以不是区别你可以有意义地在"名为x的函数之间进行,它不接受任何参数,只返回3"和"对上述功能的调用",甚至是那些和"整数3"。 Python不是那样的。 (例如,您可以使用零参数调用来表示"现在调用逻辑&#34 ;;但是这会打破special cases aren't special enough,并且需要额外的一对括号用于您需要的简单情况#39;实际上想要做任何讨论。)

functools.partial是在Python中部分应用函数的开箱即用解决方案。不幸的是,反复呼叫partial添加更多" curried"参数的效率并不高(在引擎盖下会有嵌套的partial个对象)。但是,它更灵活;特别是,您可以将其用于没有任何特殊装饰的现有功能。

答案 1 :(得分:1)

您可以为自己实现与functools.partial示例相同的内容:

def curry (prior, *additional):
    def curried(*args):
        return prior(*(args + additional))
    return curried

def add(*args):
    return sum(args)

x = curry(add, 3,4,5)
y = curry(b, 100)
print y(200)
# 312

curry视为函数工厂而不是装饰器可能更容易;从技术上讲,这是装饰器的所有功能,但装饰器使用模式是静态的,其中工厂是您希望作为操作链的一部分调用的东西。

你可以在这里看到我从add开始作为咖喱的参数而不是add(1)或者其他东西:工厂签名是<callable>, *<args>。这可以解决原始帖子的评论中的问题。

答案 2 :(得分:0)

事实1:为可变函数实现自动currying功能根本不可能。

事实2:你可能不是在寻找咖喱,如果你想要传递给它的功能*知道*它会被咖喱,以使其表现不同。

如果您需要的是一种方法来讨论可变参数函数,您应该在下面的这些行中使用某些东西(使用您自己的剪切):

def curryN(arity, func):
    """curries a function with a pre-determined number of arguments"""
    def curried(*args, **kwargs):
        if len(args) + len(kwargs) >= arity:
            return func(*args, **kwargs)
        return (lambda *args2, **kwargs2:
            curried(*(args + args2), **dict(kwargs, **kwargs2)))
    return curried

def curry(func):
    """automatically curries a function"""
    return curryN(func.__code__.co_argcount, func);

这样你可以做到:

def summation(*numbers):
    return sum(numbers);

sum_two_numbers = curryN(2, summation)
sum_three_numbers = curryN(3, summation)
increment = curryN(2, summation)(1)
decrement = curryN(2, summation)(-1)

答案 3 :(得分:0)

我认为这是一个不错的解决方案:

from copy import copy
import functools


def curry(function):

  def inner(*args, **kwargs):
    partial = functools.partial(function, *args, **kwargs)
    signature = inspect.signature(partial.func)
    try:
      signature.bind(*partial.args, **partial.keywords)
    except TypeError as e:
      return curry(copy(partial))
    else:
      return partial()

  return inner

这仅允许您以自动方式递归调用functools.partial

def f(x, y, z, info=None):
  if info:
    print(info, end=": ")
  return x + y + z

g = curry_function(f)
print(g)
print(g())
print(g(2))
print(g(2,3))
print(g(2)(3))
print(g(2, 3)(4))
print(g(2)(3)(4))
print(g(2)(3, 4))
print(g(2, info="test A")(3, 4))
print(g(2, info="test A")(3, 4, info="test B"))

输出:

<function curry.<locals>.inner at 0x7f6019aa6f28>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a0d0>
9
9
9
test A: 9
test B: 9