Каррирование Python и частичное

Aug 19 2020

Выполняя упражнения по программированию на codewars.com, я столкнулся с упражнением по каррированию и частичным функциям.

Будучи новичком в программировании и новичком в этой теме, я поискал в Интернете информацию по этой теме и довольно далеко продвинулся в решении упражнения. Однако сейчас я наткнулся на препятствие, которое, похоже, не могу преодолеть, и здесь ищу толчок в правильном направлении.

Упражнение довольно простое: напишите функцию, которая может каррировать и / или разделять любую функцию ввода и оценивать функцию ввода после того, как будет предоставлено достаточно входных параметров. Функция ввода может принимать любое количество входных параметров. Кроме того, функция curry / partial должна быть очень гибкой в ​​том, как она вызывается, и иметь возможность обрабатывать множество различных способов вызова функции. Кроме того, функцию 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еще нужно моему оригиналу (уменьшая это число каждый раз, когда я разделяю функцию с любыми заданными начальными аргументами).

Мне еще многое предстоит узнать о программировании и каррировании / частичном использовании, поэтому, скорее всего, я выбрал не самый удобный подход. Но я бы хотел научиться. Для меня трудность в этом упражнении заключается в сочетании частичного и карри, т. Е. Выполнения цикла карри с частичным разделением любых начальных аргументов, которые встречаются.

Ответы

1 Ava Aug 20 2020 at 13:27

Попробуйте это.

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, для выполнения всех внутренних работ, таких как, например, триггер (не волнуйтесь, нам там не нужен триггер ;-)).

Всякий раз, когда есть функция, которая принимает произвольные аргументы, вы всегда можете создать экземпляр этого класса, передав функцию. Но на этот раз я оставляю это на вас.

Nishant Aug 21 2020 at 02:46

Я не уверен насчет каррирования , но если вам нужен простой генератор частичных функций, вы можете попробовать что-то вроде этого:

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, имеют эту функцию, встроенную в язык.