简体   繁体   English

Python 局部套用

[英]Python currying and partial

While doing programming exercises on codewars.com, I encountered an exercise on currying and partial functions.在 codewars.com 上做编程练习时,我遇到了一个关于柯里化和偏函数的练习

Being a novice in programming and new to the topic, I searched on the inte.net for information on the topic and got quite far into solving the exercise.作为编程新手和该主题的新手,我在 inte.net 上搜索了有关该主题的信息,并深入了解如何解决该练习。 However I have now stumbled upon an obstacle I can't seem to overcome and am here looking for a nudge in the right direction.然而,我现在偶然发现了一个我似乎无法克服的障碍,我在这里寻找正确方向的推动力。

The exercise is rather simple: write a function that can curry and/or partial any input function and evaluates the input function once enough input parameters are supplied.练习相当简单:编写一个 function,它可以柯里化和/或部分输入 function,并在提供足够的输入参数后评估输入 function。 The input function can accept any number of input parameters.输入 function 可以接受任意数量的输入参数。 Also the curry/partial function should be very flexible in how it is called, being able to handle many, many different ways of calling the function. Also, the curry/partial function is allowed to be called with more inputs than required by the input function, in that case all the excess inputs need to be ignored.此外,curry/partial function 在调用方式上应该非常灵活,能够处理调用 function 的许多不同方式。此外,curry/partial function 允许使用比输入所需更多的输入来调用function,在这种情况下,所有多余的输入都需要忽略。

Following the exercise link, all the test cases can be found that the function needs to be able to handle.跟随练习链接,可以找到function需要能够处理的所有测试用例。

The code I came up with is the following:我想出的代码如下:

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

Now this code fails when the following test is executed:现在,当执行以下测试时,此代码失败:

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

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

The reason this fails is because curry_partial(add, a) returns a function handle to the function g .失败的原因是因为curry_partial(add, a)将 function 句柄返回到 function g In the second call, curry_partial(<function_handle to g>, b) , the calculation minArgs = len(signature(func).parameters) doesn't work like I want it to, because it will now calculate how many input arguments function g requires (which is 1 : ie *myArgs ), and not how many the original func still requires.在第二次调用中, curry_partial(<function_handle to g>, b) ,计算minArgs = len(signature(func).parameters)不像我想要的那样工作,因为它现在将计算多少输入 arguments function g需要(即1 :即*myArgs ),而不是原始func仍然需要多少。 So the question is, how can I write my code such that I can keep track of how many input arguments my original func still needs (reducing that number each time I am partialling the function with any given initial arguments).所以问题是,我如何编写我的代码,以便我可以跟踪我的原始func仍然需要多少输入 arguments(每次我使用任何给定的初始参数对 function 进行分部时减少该数字)。

I still have much to learn about programming and currying/partial, so most likely I have not chosen the most convenient approach.关于编程和柯里化/部分,我还有很多东西要学习,所以很可能我没有选择最方便的方法。 But I'd like to learn.但我想学习。 The difficulty in this exercise for me is the combination of partial and curry, ie doing a curry loop while partialling any initial arguments that are encountered.对我来说,这个练习的难点在于 partial 和 curry 的组合,即在对遇到的任何初始 arguments 进行 partially 时执行 curry 循环。

Try this out.试试这个。

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

This indeed isn't very good by design.这确实不是很好的设计。 Instead you should use class , to do all the internal works like, for example, the flip-flop (don't worry we don't need any flip-flop there;-)).相反,您应该使用class来完成所有内部工作,例如触发器(别担心,我们那里不需要任何触发器;-))。

Whenever there's a function that takes arbitrary arguments, you can always instantiate that class passing the function. But this time however, I leave that on you.每当有一个 function 接受任意 arguments 时,您总是可以实例化 class 传递 function。但是这次,我把它留给你。

I am not sure about currying , but if you need a simple partial function generator, you could try something like this:我不确定currying ,但如果你需要一个简单的部分 function 生成器,你可以尝试这样的事情:

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

Even if it doesn't solve your original problem, see if you can use the above code to reduce code repetition (I am not sure, but I think there is some code repetition in the inner function?);就算不能解决你原来的问题,看看你能不能用上面的代码来减少代码重复(我不确定,但我觉得里面function有一些代码重复?); that will make the subsequent problems easier to solve.这将使后续问题更容易解决。

There could be functions in the standard library that already solve this problem.标准库中可能有一些函数已经解决了这个问题。 Many pure functional languages like Haskell have this feature built into the language.许多纯函数式语言,如 Haskell,都在语言中内置了此功能。

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

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