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