Каррирование Python и частичное
Выполняя упражнения по программированию на 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еще нужно моему оригиналу (уменьшая это число каждый раз, когда я разделяю функцию с любыми заданными начальными аргументами).
Мне еще многое предстоит узнать о программировании и каррировании / частичном использовании, поэтому, скорее всего, я выбрал не самый удобный подход. Но я бы хотел научиться. Для меня трудность в этом упражнении заключается в сочетании частичного и карри, т. Е. Выполнения цикла карри с частичным разделением любых начальных аргументов, которые встречаются.
Ответы
Попробуйте это.
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, для выполнения всех внутренних работ, таких как, например, триггер (не волнуйтесь, нам там не нужен триггер ;-)).
Всякий раз, когда есть функция, которая принимает произвольные аргументы, вы всегда можете создать экземпляр этого класса, передав функцию. Но на этот раз я оставляю это на вас.
Я не уверен насчет каррирования , но если вам нужен простой генератор частичных функций, вы можете попробовать что-то вроде этого:
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, имеют эту функцию, встроенную в язык.