Какова логика этой конкретной композиции функций 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. возможно ли это (и как?) получить то же самое , не используя для этого reduce?

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


Редактировать:

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

О 1-м пункте моего вопроса:

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

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

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

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

Как работает это лямбда-гнездовое "волшебство"?

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

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

>>> mycompositefunc(2)
0.1553124117201235

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

>>> sin(cos(sqrt(2)))
0.1553124117201235

В то время как вы получаете ошибку с

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

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

#P.S. for completeness:

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

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

Итак, я понимаю, что композиция функций будет сделана от последней до первой ( т. е.: compose(sin, cos, sqrt) => sin (cos (sqrt (x))) но " почему? " и как работает это лямбда-вложенное "волшебство"? все еще остается немного неясным для меня... Помощь/предложения очень приветствуются!

по 2-му пункту (о переписывании сочинять без сокращения)

@Martijn Pieters: ваша первая композиция ("обернутая") работает и возвращает точно такой же результат

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

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

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

149   1  

1 ответ:

Синтаксис *args, **kw как в сигнатуре lambda, так и в синтаксисе вызова-лучший способ передатьпроизвольные аргументы . Они принимают любое количество позиционных и ключевых аргументов и просто передают их следующему вызову. Вы можете записать результат внешнего 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, С sqrt g, Итак, вы получаете:

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()может быть применена к чему угодно, кроме вызываемого, при условии, что все, кроме первой функции, конечно, принимают только один аргумент.

    Ничего не найдено.

Добавить ответ:
Отменить.