Python Currying und teilweise

Aug 19 2020

Während ich Programmierübungen auf codewars.com machte, stieß ich auf eine Übung zu Curry und Teilfunktionen.

Als Programmieranfänger und Neuling im Thema suchte ich im Internet nach Informationen zum Thema und war ziemlich weit in der Lösung der Übung. Jetzt bin ich jedoch auf ein Hindernis gestoßen, das ich scheinbar nicht überwinden kann, und suche hier einen Anstoß in die richtige Richtung.

Die Übung ist ziemlich einfach: Schreiben Sie eine Funktion, die jede Eingabefunktion curry und / oder partiell ausführen kann, und wertet die Eingabefunktion aus, sobald genügend Eingabeparameter bereitgestellt wurden. Die Eingabefunktion kann eine beliebige Anzahl von Eingabeparametern akzeptieren. Auch die Curry- / Teilfunktion sollte sehr flexibel sein, wie sie aufgerufen wird, und in der Lage sein, viele, viele verschiedene Arten des Aufrufs der Funktion zu handhaben. Außerdem darf die Curry- / Teilfunktion mit mehr Eingaben aufgerufen werden, als von der Eingabefunktion benötigt werden. In diesem Fall müssen alle überschüssigen Eingaben ignoriert werden.

Wenn Sie dem Übungslink folgen, können Sie alle Testfälle finden, die die Funktion verarbeiten muss.

Der Code, den ich mir ausgedacht habe, lautet wie folgt:

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

Jetzt schlägt dieser Code fehl, wenn der folgende Test ausgeführt wird:

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

wobei add = a + b + c (richtig definierte Funktion), a = 1, b = 2, c = 3 und sum = 6.

Der Grund dafür ist, dass curry_partial(add, a)ein Funktionshandle an die Funktion zurückgegeben wird g. Beim zweiten Aufruf funktioniert curry_partial(<function_handle to g>, b)die Berechnung minArgs = len(signature(func).parameters)nicht so, wie ich es möchte, da jetzt berechnet wird, wie viele Eingabeargumente die Funktion gbenötigt ( 1dh: dh *myArgs) und nicht, wie viele das Original funcnoch benötigt. Die Frage ist also, wie ich meinen Code so schreiben kann, dass ich verfolgen kann, wie viele Eingabeargumente mein Original funcnoch benötigt (wobei diese Anzahl jedes Mal verringert wird, wenn ich die Funktion mit bestimmten Anfangsargumenten teile).

Ich muss noch viel über Programmieren und Currying / Partial lernen, daher habe ich höchstwahrscheinlich nicht den bequemsten Ansatz gewählt. Aber ich würde gerne lernen. Die Schwierigkeit in dieser Übung ist für mich die Kombination von Partial und Curry, dh eine Curry-Schleife zu machen, während alle anfänglichen Argumente, auf die man stößt, teilweise behandelt werden.

Antworten

1 Ava Aug 20 2020 at 13:27

Probieren Sie es aus.

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

Dies ist in der Tat nicht sehr gut. Stattdessen sollten classSie alle internen Arbeiten wie zum Beispiel das Flip-Flop verwenden (keine Sorge, wir brauchen dort kein Flip-Flop ;-)).

Immer wenn es eine Funktion gibt, die beliebige Argumente akzeptiert, können Sie diese Klasse, die die Funktion übergibt, instanziieren. Aber diesmal überlasse ich das Ihnen.

Nishant Aug 21 2020 at 02:46

Ich bin mir beim Curry nicht sicher , aber wenn Sie einen einfachen Teilfunktionsgenerator benötigen, können Sie Folgendes ausprobieren:

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

Auch wenn es Ihr ursprüngliches Problem nicht löst, prüfen Sie, ob Sie den obigen Code verwenden können, um die Codewiederholung zu reduzieren (ich bin nicht sicher, aber ich denke, dass die innere Funktion eine gewisse Codewiederholung enthält?). Dadurch können die nachfolgenden Probleme leichter gelöst werden.

Es könnte Funktionen in der Standardbibliothek geben, die dieses Problem bereits lösen. In vielen reinen Funktionssprachen wie Haskell ist diese Funktion in die Sprache integriert.