2015-08-08 5 views
7

Рассмотрим следующий фрагмент кода Python относительно функций состава:Какова логика этой конкретной композиции функций Python?

from functools import reduce 
def compose(*funcs): 
    # compose a group of functions into a single composite (f(g(h(..(x)..))) 
    return reduce(lambda f, g: lambda *args, **kwargs: f(g(*args, **kwargs)), funcs) 


### --- usage example: 
from math import sin, cos, sqrt 
mycompositefunc = compose(sin,cos,sqrt) 
mycompositefunc(2) 

У меня есть два вопроса:

  1. Может кто-то пожалуйста, объясните мне "логику работы" compose? (Как это работает?)
  2. Было бы возможно (и как?) Получить то же самое without using reduce для этого?

Я уже смотрел here, here и here too, моя проблема НЕ понимание того, что lambda средства или reduce делает (я думаю, что я, например, что 2 в примере использования будет несколько первый элемент funcs). Что я нахожу трудно понять, а сложность, как два lambda s получил комбинированный/вложенная и смешивают с *args, **kwargs здесь в качестве первого аргумента reduce ...


EDIT:

Прежде все, @Martijn и @Borealid, спасибо за ваши усилия и ответы и за то, что вы посвящаете мне. (Извините за задержку, я делаю это в свое свободное время и не всегда имеют аа много ...)

Хорошо, подходя к фактам сейчас ...

О 1-й точке на мой вопрос:

Прежде всего я понял, что я действительно не получил (а я надеюсь, что я сделал сейчас) об этих *args, **kwargs переменного числа аргументов до того, что по крайней мере **kwargsне является обязательным (я говорю хорошо, верно?) Это заставил меня понять, например, почему mycompositefunc(2) работает с этим только одним (не ключевым) переданным аргументом.

Я понял, что пример будет работать даже при замене этих *args, **args во внутренней лямбда простым x. Я полагаю, это связано с тем, что в этом примере все три составные функции (sin, cos, sqrt) ожидают один (и только один) параметр ... и, конечно же, возвращают один результат ... так, более конкретно, он работает, потому что составная функция ожидает только один параметр (следующие остальные будут естественно получить только один аргумент здесь, это результат предыдущих скомпонованных функций, поэтому вы НЕ МОЖЕТЕ составить функции, которые ожидают более одного аргумента после первого ... Я знаю, что это немного искажать, но я думаю, что вы получили то, что я пытаюсь объяснить ...)

Сейчас подходит к тому, что остается неясным, реальный вопрос для меня здесь:

lambda f, g: lambda *args, **kwargs: f(g(*args, **kwargs)) 

Как работает эта лямбда «магия»?

Со всем уважением вы заслуживаете, и я приношу вам, мне кажется, как вы оба неправы приходят к выводу, окончательный результат должен быть: sqrt(sin(cos(*args, **kw))). На самом деле этого не может быть, порядок работы функции sqrt явно отменяется: он не последний, а первый.

Я говорю это потому, что:

>>> mycompositefunc(2) 
0.15531241172

его результат равен

>>> sin(cos(sqrt(2))) 
0.15531241172

тогда вы получите ошибку с

>>> sqrt(sin(cos(2))) 
[...] 
ValueError: math domain error 

(это связано с попытками SquareRoot отрицательным поплавок)

#P.S. for completeness: 

>>> sqrt(cos(sin(2))) 
0.7837731062727799 

>>> cos(sin(sqrt(2))) 
0.5505562169613818 

Итак, я понимаю, что состав функций будет сделан от последнего к первому (т. Е. Составить (sin, cos, sqrt) => sin (cos (sqrt (x)))), но «Зачем?, как это делает лямбда вложенности„магия“работает? до сих пор остается неясным, немного для меня ... Помощь/Предложения очень оценили!

На 2-й точке (о перезаписи сочинить без уменьшения)

@Martijn Питерс: ваш первый Compose (далее "завернуты" один) работает и возвращает точно такой же результат

>>> mp_compfunc = mp_compose(sin,cos,sqrt) 
>>> mp_compfunc(2) 
0.15531241172

развернутая версия, instea d, к сожалению, петли до RuntimeError: maximum recursion depth exceeded ...

@Borealid: ваш пример foo/bar не получит больше двух функций для композиции, но я думаю, что это было просто для объяснений, не предназначенных для ответа на вторую точку, не так ли?

ответ

5

Синтаксис *args, **kw как в синтаксисе подписи и вызова lambda - это лучший способ передать произвольные аргументы. Они принимают любое количество аргументов positional и keyword и просто передают их на следующий вызов. Вы можете написать результат внешнего lambda как:

def _anonymous_function_(*args, **kw): 
    result_of_g = g(*args, **kw) 
    return f(result_of_g) 
return _anonymous_function 

compose функция может быть переписана без reduce() так:

def compose(*funcs): 
    wrap = lambda f, g: lambda *args, **kw: f(g(*args, **kw)) 
    result = funcs[0] 
    for func in funcs[1:]: 
     result = wrap(result, func) 
    return result 

Это делает точно такую ​​же вещь, как reduce() вызова; вызовите лямбду для цепочки функций.

Итак, первые две функции в последовательности являются sin и cos, и они заменяются:

lambda *args, **kw: sin(cos(*args, **kw)) 

Это тогда передается на вызов следующий как f с sqrtg, так что вы получить:

lambda *args, **kw: (lambda *args, **kw: sin(cos(*args, **kw)))(sqrt(*args, **kw))) 

, который может быть упрощена:

lambda *args, **kw: sin(cos(sqrt(*args, **kw))) 

потому что f() - это лямбда, которая передает свои аргументы во вложенный вызов sin(cos()).

В конце концов, то, вы создали функцию, которая вызывает sqrt(), результат которого передается cos(), а выход, который затем передается sin(). *args, **kw позволяет передавать любое количество аргументов или аргументов ключевого слова, поэтому функция compose() может быть применена к чем-либо, чем может быть вызвано, при условии, что все, кроме первой функции, принимают только один аргумент, конечно.

+1

Лучше использовать 'lambda x: x' в качестве начального значения для' reduce' или цикл в вашем ответе: таким образом 'compose()' является правильным составом нулевых функций - функция это ничего не делает. – Lynn

+0

@Mauris: но это не отразило бы результат функции 'reduce()', опубликованной в вопросе. –

+0

@Mauris: Martijn прямо на том, что он не будет отражать оригинальную функцию, опубликованную в вопросе, приятное предложение в любом случае +1! – danicotra

Смежные вопросы