Я ищу хороший функциональный способ сделать следующее:
def add(x, y):
return x + y
def neg(x):
return -x
def c(x, y):
# Apply neg to inputs for add
_x = neg(x)
_y = neg(y)
return add(_x, _y)
neg_sum = c(2, 2) # -4
Кажется, это связано с каррированием, но все примеры, которые я могу найти, используют функции, которые имеют только одну входную переменную. Я хотел бы что-то похожее на это:
def add(x, y):
return x + y
def neg(x):
return -x
c = apply(neg, add)
neg_sum = c(2, 2) # -4
Это довольно прямой способ сделать это:
def add(x, y):
return x + y
def neg(x):
return -x
def apply(g, f):
# h is a function that returns
# f(g(arg1), g(arg2), ...)
def h(*args):
return f(*map(g, args))
return h
# or this:
# def apply(g, f):
# return lambda *args: f(*map(g, args))
c = apply(neg, add)
neg_sum = c(2, 2) # -4
Обратите внимание, что когда вы используете *myvar
в качестве аргумента в определении функции, myvar
становится списком всех полученных аргументов, не являющихся ключевыми словами. И если вы вызываете функцию с *expression
в качестве аргумента, то все элементы в expression
распаковываются и отправляются в качестве отдельных аргументов в функцию. Я использую эти два поведения, чтобы заставить h
принимать неизвестный список аргументов, затем применять функцию g
к каждому из них (с map
), а затем передавать их все в качестве аргументов f
.
Уточняя ваш ответ: как бы вы закодировали функцию компоновки, которая работала бы как для add_neg = compose(add, neg)
, так и для neg_add = compose(neg,add)
?
Другой подход, в зависимости от того, насколько расширяемым вам это нужно, заключается в создании объекта, который реализует ваши операторные методы, каждый из которых возвращает один и тот же объект, что позволяет вам объединять операторы в цепочку в произвольном порядке.
Если вы можете справиться с тем, что он всегда возвращает список, возможно, вы сможете заставить его работать.
class mathifier:
def __init__(self,values):
self.values = values
def neg(self):
self.values = [-value for value in self.values]
return self
def add(self):
self.values = [sum(self.values)]
return self
print (mathifier([2,3]).neg().add().values)
И вы все еще можете получить свою именованную функцию для любого набора связанных функций:
neg_add = lambda x : mathifier(x).neg().add()
print(neg_add([2,3]).values)
Из ответа Маттиаса Фриппа я спросил себя: я хотел бы сочинять add
и neg
в обоих направлениях: add_neg(*args)
и neg_add(*args)
. Это требует немного взломать предложение Матиаса. Идея состоит в том, чтобы получить некоторый намек на арность (количество аргументов) функций для составления. Эта информация получена с небольшим самоанализом благодаря модулю inspect
. Имея это в виду, мы адаптируем способ передачи аргументов через цепочку функций. Основное предположение здесь состоит в том, что мы имеем дело с реальными функциями в математическом смысле, то есть с функциями, возвращающими ОДНО число с плавающей точкой и принимающими по крайней мере один аргумент.
from functools import reduce
from inspect import getfullargspec
def arity_one(func):
spec = getfullargspec(func)
return len(spec[0])==1 and spec[1] is None
def add(*args):
return reduce(lambda x,y:x+y, args, 0)
def neg(x):
return -x
def compose(fun1,fun2):
def comp(*args):
if arity_one(fun2): return fun1(*(map( fun2, args)))
else: return fun1(fun2(*args))
return comp
neg_add = compose(neg, add)
add_neg = compose(add, neg)
print(f"-2+(-3) = {add_neg(2, 3)}")
print(f"-(2+3) = {neg_add(2, 3)}")
Решение все еще очень adhoc...
Вы хотите
apply(g, f)
создать новую функциюc
, которая применяет функциюg
ко всем своим аргументам, а затем передает результаты функцииf
. Это предполагает, что функцииc
иf
принимают одинаковое количество аргументов (возможно, несколько) и что обеc
иf
возвращают одно значение. Это правильно? Я чувствую, что могут быть некоторые другие случаи, когда вы хотите построитьf(g(h(x)), g(h(y)))
илиh(f(g(x), g(y)))
, и для этого может не хватать симметрии. Или, может быть, это так, если вы просто вызываетеapply
несколько раз, иногда с функциями с одним аргументом.