Python 柯里化和部分

问题描述 投票:0回答:3

在 codewars.com 上做编程练习时,我遇到了关于柯里化和部分函数的练习

作为编程新手和该主题的新手,我在互联网上搜索了有关该主题的信息,并深入解决了该练习。然而,我现在偶然发现了一个我似乎无法克服的障碍,我在这里寻找正确方向的推动。

练习相当简单:编写一个可以柯里化和/或部分化任何输入函数的函数,并在提供足够的输入参数后评估输入函数。输入函数可以接受任意数量的输入参数。此外,柯里化/偏函数的调用方式应该非常灵活,能够处理许多不同的函数调用方式。此外,允许使用比输入函数所需更多的输入来调用 curry/partial 函数,在这种情况下,所有多余的输入都需要被忽略。

按照练习链接,可以找到该函数需要能够处理的所有测试用例。

我想出的代码如下:

from functools import partial
from inspect import signature

def curry_partial(func, *initial_args):
    """ Generates a 'curried' version of a function. """

    # Process any initial arguments that where given. If the number of arguments that are given exceeds 
    # minArgs (the number of input arguments that func needs), func is evaluated

    minArgs = len(signature(func).parameters)
    if initial_args:
        if len(initial_args) >= minArgs: 
            return func(*initial_args[:minArgs])

        func = partial(func, *initial_args)
        minArgs = len(signature(func).parameters)

    
    # Do the currying
    def g(*myArgs):
        nonlocal minArgs

        # Evaluate function if we have the necessary amount of input arguments
        if minArgs is not None and minArgs <= len(myArgs):
                return func(*myArgs[:minArgs]) 
            
        def f(*args):
            nonlocal minArgs
            newArgs = myArgs + args if args else myArgs

            if minArgs is not None and minArgs <= len(newArgs):
                return func(*newArgs[:minArgs])
            else:
                return g(*newArgs)  
        return f
    return g

现在,当执行以下测试时,此代码将失败:

test.assert_equals(curry_partial(curry_partial(curry_partial(add, a), b), c), sum)

其中 add = a + b + c(正确定义的函数),a = 1,b = 2,c = 3,sum = 6。

失败的原因是

curry_partial(add, a)
返回函数
g
的函数句柄。在第二次调用
curry_partial(<function_handle to g>, b)
中,计算
minArgs = len(signature(func).parameters)
并不像我想要的那样工作,因为它现在将计算函数
g
需要多少个输入参数(即
1
:即
*myArgs
) ,而不是原来的
func
还需要多少。所以问题是,我如何编写代码,以便跟踪我的原始
func
仍然需要多少个输入参数(每次使用任何给定的初始参数部分化函数时减少该数量)。

关于编程和柯里化/部分,我还有很多东西需要学习,所以很可能我没有选择最方便的方法。但我想学习。对我来说,这个练习的困难在于部分和柯里化的结合,即在部分化遇到的任何初始参数的同时进行柯里化循环。

python partial currying
3个回答
1
投票

试试这个。

from inspect import signature

# Here `is_set` acts like a flip-flop
is_set = False
params = 0

def curry_partial(func, *partial_args):
    """
    Required argument: func
    Optional argument: partial_args
    Return:
        1) Result of the `func` if
           `partial_args` contains
           required number of items.
        2) Function `wrapper` if `partial_args`
           contains less than the required
           number of items.
    """

    global is_set, params
    
    if not is_set:
        is_set = True
        
        # if func is already a value
        # we should return it
        try: params = len(signature(func).parameters)
        except: return func
    
    try:
        is_set = False
        return func(*partial_args[:params])
    
    except:
        is_set = True
    
        def wrapper(*extra_args):
            """
            Optional argument: extra_args
            Return:
                1) Result of the `func` if `args`
                   contains required number of
                   items.
                2) Result of `curry_partial` if
                   `args` contains less than the
                   required number of items.
            """
            
            args = (partial_args + extra_args)
            
            try:
                is_set = False
                return func(*args[:params])
            except:
                is_set = True
                return curry_partial(func, *args)
    
    return wrapper

这在设计上确实不太好。相反,您应该使用

class
来完成所有内部工作,例如触发器(不用担心,我们不需要任何触发器;-))。

每当有一个函数接受任意参数时,您始终可以实例化该类并传递该函数。但这一次,我把它留给你了。


1
投票

我不确定currying,但如果你需要一个简单的部分函数生成器,你可以尝试这样的事情:

from functools import partial
from inspect import signature

def execute_or_partial(f, *args):
    max = len(signature(f).parameters)
    if len(args) >= max: 
        return f(*args[:max])
    else:
        return partial(f, *args)

s = lambda x, y, z: x + y + z

t = execute_or_partial(s, 1)
u = execute_or_partial(t, 2)
v = execute_or_partial(u, 3)

print(v)

or

print(execute_or_partial(execute_or_partial(execute_or_partial(s, 1), 2), 3))

即使没有解决你原来的问题,看看是否可以使用上面的代码来减少代码重复(我不确定,但我认为内部函数中有一些代码重复?);这样后续的问题就更容易解决了。

标准库中可能有函数已经解决了这个问题。许多纯函数式语言(如 Haskell)都内置了此功能。


0
投票

聚会迟到了,但这也许会对某人有所帮助。它支持关键字参数,不需要全局状态,完全起作用,并且机会主义地执行(即,第一次有足够的参数)。语法非常简洁:

@curried
def test(x, y, z=4):
    return x, y, z

# all of these return (1, 2, 3)
test(1)(z=3)(y=2)
test(1)(y=2, z=3)
test(x=1, y=2, z=3)
test(1, 2, 3)

# these return (1, 2, 4)
test(1, 2)
test(1, y=2)
test(y=2)(x=1)
test(y=0)(y=2)(x=1)  # you can override kwargs by giving them again

# this works too
test2 = test(z=3)(1)
test2(21), test2(22)  # returns ((1, 21, 3), (1, 22, 3))

来源:

from functools import partial
from inspect import signature, _empty

def curried(f):
    def inner(*args, **kwds):
        new_f = partial(f, *args, **kwds)
        params = signature(new_f, follow_wrapped=True).parameters
        if all(params[p].default != _empty for p in params):
            return new_f()
        else:
            return curried(new_f)
    return inner
© www.soinside.com 2019 - 2024. All rights reserved.