Python curry i częściowe
Robiąc ćwiczenia programistyczne na codewars.com, natknąłem się na ćwiczenie z curry i funkcji częściowych.
Będąc nowicjuszem w programowaniu i nowym tematem, szukałem w Internecie informacji na ten temat i zająłem się rozwiązaniem zadania. Jednak teraz natknąłem się na przeszkodę, której nie mogę pokonać i szukam tu posunięcia we właściwym kierunku.
Ćwiczenie jest raczej proste: napisz funkcję, która może sprawdzić i / lub podzielić dowolną funkcję wejściową i ocenia funkcję wejściową po dostarczeniu wystarczającej ilości parametrów wejściowych. Funkcja wejściowa może przyjmować dowolną liczbę parametrów wejściowych. Również funkcja curry / częściowa powinna być bardzo elastyczna w sposobie jej wywoływania, będąc w stanie obsłużyć wiele, wiele różnych sposobów wywoływania funkcji. Ponadto funkcja curry / częściowa może być wywoływana z większą liczbą danych wejściowych niż jest to wymagane przez funkcję wejściową, w takim przypadku wszystkie nadmiarowe dane wejściowe muszą zostać zignorowane.
Po odsyłaczu do ćwiczeń można znaleźć wszystkie przypadki testowe, które funkcja musi obsługiwać.
Kod, który wymyśliłem, jest następujący:
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
Teraz ten kod kończy się niepowodzeniem, gdy wykonywany jest następujący test:
test.assert_equals(curry_partial(curry_partial(curry_partial(add, a), b), c), sum)
gdzie add = a + b + c (poprawnie zdefiniowana funkcja), a = 1, b = 2, c = 3, a sum = 6.
Przyczyną niepowodzenia jest to, że curry_partial(add, a)zwraca uchwyt funkcji do funkcji g. W drugim wywołaniu curry_partial(<function_handle to g>, b)obliczenie minArgs = len(signature(func).parameters)nie działa tak, jak bym chciał, ponieważ oblicza teraz, ile argumentów wejściowych gwymaga funkcja ( 1czyli: tj. *myArgs), A nie ile funcwymaga jeszcze oryginał . Pytanie brzmi więc, w jaki sposób mogę napisać swój kod tak, aby móc śledzić, ile argumentów wejściowych funcnadal potrzebuje mój pierwotny (zmniejszając tę liczbę za każdym razem, gdy częściowo dzielę funkcję z podanymi argumentami początkowymi).
Nadal muszę się wiele nauczyć na temat programowania i curry / częściowego, więc najprawdopodobniej nie wybrałem najwygodniejszego podejścia. Ale chciałbym się nauczyć. Trudność w tym ćwiczeniu polega dla mnie na połączeniu częściowych i curry, tj. Wykonywanie pętli curry podczas częściowego przedstawiania wszelkich napotkanych argumentów początkowych.
Odpowiedzi
Wypróbuj to.
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
To rzeczywiście nie jest zbyt dobre z założenia. Zamiast tego powinieneś użyć class, do wszystkich wewnętrznych prac, takich jak na przykład przerzutnik (nie martw się, nie potrzebujemy tam żadnego przerzutnika ;-)).
Zawsze, gdy istnieje funkcja, która przyjmuje dowolne argumenty, zawsze można utworzyć wystąpienie tej klasy, przekazując funkcję. Ale tym razem zostawiam to na tobie.
Nie jestem pewien co do curry , ale jeśli potrzebujesz prostego generatora funkcji częściowych, możesz spróbować czegoś takiego:
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))
Nawet jeśli nie rozwiąże to twojego pierwotnego problemu, sprawdź, czy możesz użyć powyższego kodu, aby zmniejszyć liczbę powtórzeń kodu (nie jestem pewien, ale myślę, że w funkcji wewnętrznej jest jakieś powtórzenie kodu?); ułatwi to rozwiązanie kolejnych problemów.
W bibliotece standardowej mogą znajdować się funkcje, które już rozwiązują ten problem. Wiele czysto funkcjonalnych języków, takich jak Haskell, ma tę funkcję wbudowaną w język.