Python Currying und teilweise
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
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.
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.