简体   繁体   English

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

[英]Python currying with any number of variables

I am trying to use currying to make a simple functional add in Python. 我正在尝试使用currying在Python中进行简单的功能添加。 I found this curry decorator 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

Now this is great because I can do some simple currying: 现在这很棒,因为我可以做一些简单的讨论:

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

But this only works for exactly three variables. 但这仅适用于三个变量。 How do I write the function foo so that it can accept any number of variables and still be able to curry the result? 如何编写函数foo以便它可以接受任意数量的变量并且仍然可以调整结果? I've tried the simple solution of using *args but it didn't work. 我尝试过使用* args的简单解决方案,但它没有用。

Edit: I've looked at the answers but still can't figure out how to write a function that can perform as shown below: 编辑:我已经查看了答案,但仍然无法弄清楚如何编写一个可以执行如下所示的函数:

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

Arguably, explicit is better than implicit : 可以说, 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 explained the design problem with what you're trying to do - a call to the curried function would be ambiguous between "add more curried arguments" and "invoke the logic". @JohnKugelman用你正在尝试做的事情解释了设计问题 - 在“添加更多curried参数”和“调用逻辑”之间调用curried函数将是模棱两可的。 The reason this isn't a problem in Haskell (where the concept comes from) is that the language evaluates everything lazily, so there isn't a distinction you can meaningfully make between "a function named x that accepts no arguments and simply returns 3" and "a call to the aforementioned function", or even between those and "the integer 3". 这个问题在Haskell(概念来自于)中不是问题的原因是语言懒惰地评估所有内容 ,因此在“名为x的函数之间没有任何参数并且只返回3”之间没有区别 “和”对上述函数的调用“,或甚至在那些和”整数3“之间。 Python isn't like that. Python不是那样的。 (You could, for example, use a zero-argument call to signify "invoke the logic now"; but that would break special cases aren't special enough , and require an extra pair of parentheses for simple cases where you don't actually want to do any currying.) (例如,您可以使用零参数调用来表示“立即调用逻辑”;但是这会打破special cases aren't special enough ,并且对于您实际上special cases aren't special enough需要额外的一对括号想做任何讨论。)

functools.partial is an out-of-box solution for partial application of functions in Python. functools.partial是一个开箱即用的解决方案,用于在Python中部分应用函数。 Unfortunately, repeatedly calling partial to add more "curried" arguments isn't quite as efficient (there will be nested partial objects under the hood). 不幸的是,反复调用partial以添加更多“curried”参数并不是那么有效(在引擎盖下会有嵌套的partial对象)。 However, it's much more flexible; 但是,它更灵活; in particular, you can use it with existing functions that don't have any special decoration. 特别是,您可以将它用于没有任何特殊装饰的现有功能。

You can implement the same thing as the functools.partial example for yourself like this: 您可以为自己实现与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

It may be easier to think of curry as a function factory rather than a decorator; curry视为功能工厂而不是装饰者可能更容易; technically that's all a decorator does but the decorator usage pattern is static where a factory is something you expect to be invoking as part of a chain of operations. 从技术上讲,这是装饰器的所有功能,但装饰器使用模式是静态的,其中工厂是您希望作为操作链的一部分调用的东西。

You can see here that I'm starting with add as an argument to curry and not add(1) or something: the factory signature is <callable>, *<args> . 你可以在这里看到我开始使用add作为咖喱的参数而不是add(1)或者其他东西:工厂签名是<callable>, *<args> That gets around the problem in the comments to the original post. 这可以解决原始帖子的评论中的问题。

FACT 1: It is simply impossible to implement an auto currying function for a variadic function. 事实1:对于可变功能实现自动曲线功能根本不可能。

FACT 2: You might not be searching for curry, if you want the function that will be passed to it * to know* that its gonna be curried, so as to make it behave differently. 事实2:你可能不是在寻找咖喱,如果你想要传递给它的功能*知道*它将被咖喱,以使其表现不同。

In case what you need is a way to curry a variadic function, you should go with something along these lines below (using your own snipped): 如果您需要的是一种方法来讨论可变参数函数,您应该在下面的这些行中使用某些东西(使用您自己的剪切):

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);

this way you can do: 这样你可以做到:

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)

I think this is a decent solution: 我认为这是一个不错的解决方案:

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

This just allow you to call functools.partial recursively in an automated way: 这只是允许您以自动方式递归调用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"))

Outputs: 输出:

<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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM