Складанне функцыі ў Python

У мяне ёсць набор функцый, і я стараюся, каб вырабіць адну функцыю, якая складаецца з кампазіцыі элементаў у маім масіве. Мой падыход:

def compose(list):
    if len(list) == 1:
        return lambda x:list[0](x)
    list.reverse()
    final=lambda x:x
    for f in list:
        final=lambda x:f(final(x))
    return final

Гэты метад не здаецца, працуе, дапамога будзе ацэнена.

(Я рэверсіўны спіс, таму што гэта парадак кампазіцыі Я хачу, каб функцыі былі)

14

9 адказы

Самы просты падыход быў бы першым, каб напісаць склад 2 функцый:

def compose2(f, g):
    return lambda *a, **kw: f(g(*a, **kw))

А затым выкарыстоўвайце </паменшыць код> злажыць некалькі функцый:

def compose(*fs):
    return reduce(compose2, fs)

Ці вы можаце выкарыстоўваць некаторыя бібліятэкі , які ўжо ўтрымлівае злажыць функцыю.

22
дададзена
Добры адказ - уключае ў сябе просты прыклад, а таксама спасылаецца на LIB. Я хацеў бы таксама прапанаваць jaraco.functools ў іншую бібліятэку для разгляду ,
дададзена аўтар Jason R. Coombs, крыніца
def compose (*functions):
    def inner(arg):
        for f in reversed(functions):
            arg = f(arg)
        return arg
    return inner

прыклад:

>>> def square (x):
        return x ** 2
>>> def increment (x):
        return x + 1
>>> def half (x):
        return x/2

>>> composed = compose(square, increment, half) # square(increment(half(x)))
>>> composed(5) # square(increment(half(5))) = square(increment(2.5)) = square(3.5) = 12,25
12.25
14
дададзена

рэкурсіўная рэалізацыя

Here's a рэкурсіўная рэалізацыя, which I have had yet to see:

def compose(*funcs):
    def inner(data, funcs=funcs):
        return inner(funcs[-1](data), funcs[:-1]) if funcs else data
    return inner

Я не чакаю, што гэта будзе вельмі прадукцыйным, хоць, як гэта робіць новы картэж аргументаў кожны рэкурсіўны выклік.

Параўнанне ўсіх прапаноў:

Давайце праверым некаторыя з гэтых рэалізацый і вызначыць, што з'яўляецца найбольш прадукцыйнымі, спачатку некаторымі асобнымі функцыямі аргументу (Дзякуй соваць):

def square (x):
    return x ** 2

def increment (x):
    return x + 1

def half (x):
    return x/2

Вось нашы рэалізацыі, я падазраю, што итеративный версія з'яўляецца другім найбольш эфектыўным (ручным Compose будзе, натуральна, самым хуткім).

from functools import reduce

def recursive_compose(*funcs):
    def inner(data, funcs=funcs):
        return inner(funcs[-1](data), funcs[:-1]) if funcs else data
    return inner

def iterative_compose(*functions):
    def inner(arg):
        for f in reversed(functions):
            arg = f(arg)
        return arg
    return inner

def _compose2(f, g):
    return lambda *a, **kw: f(g(*a, **kw))

def reduce_compose1(*fs):
    return reduce(_compose2, fs)

def reduce_compose2(*funcs):
    """bug fixed - added reversed()"""
    return lambda x: reduce(lambda acc, f: f(acc), reversed(funcs), x)

І праверыць іх:

import timeit
composes = (recursive_compose, iterative_compose, 
            reduce_compose1, reduce_compose2)

def manual():
    return square(increment(half(5)))

print('manual compose', min(timeit.repeat(manual)), manual())

for compose in composes:
    fn = lambda: compose(square, increment, half)(5)
    result = min(timeit.repeat(fn))
    print(compose.__name__, result, fn())

вынікі

І мы атрымліваем наступную выснову (такую ​​ж велічыню і долі ў Python 2 і 3):

manual compose 0.607658714056015 12.25
recursive_compose 1.929560380987823 12.25
iterative_compose 1.3319460819475353 12.25
reduce_compose1 2.0850532418116927 12.25
reduce_compose2 1.5899418010376394 12.25

І мае чаканні пацвердзіліся: хуткі, вядома, ручной функцыі кампазіцыі з наступнай ітэрацыйныя рэалізацыі. Рэкурсіўная версія значна павольней - верагодна, так як новы кадр стэка ствараецца пры кожным выкліку функцыі і новы набор функцый ствараецца для кожнай функцыі.

8
дададзена

Яна не працуе, таму што ўсе ананімныя функцыі, якія вы ствараеце ў цыкле ставяцца да таго ж пераменнаму цыкле і, такім чынам, падзяляюць яго канчатковы кошт.

Як хутка выправіць, вы можаце замяніць прызначэнне:

final = lambda x, f=f, final=final: f(final(x))

Ці, вы можаце вярнуць лямбда з функцыі:

def wrap(accum, f):
    return lambda x: f(accum(x))
...
final = wrap(final, f)

Для таго, каб зразумець, што адбываецца, паспрабуйце гэты эксперымент:

>>> l = [lambda: n for n in xrange(10)]
>>> [f() for f in l]
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]

Гэты вынік здзіўляе многіх людзей, якія чакаюць, каб вынік [0, 1, 2, ...] . Тым не менш, усе кропкі лямбда да таго ж п зменнай, і ўсе яны ставяцца да канчатковага значэння, якое 9. У вашым выпадку, усе версіі Канчатковы , які, як мяркуецца, гнездавацца ў канчатковым выніку са спасылкай на той жа е і, што яшчэ горш, да таго ж канчатковы .

Тэма лямбда і завес ў Python быў ўжо ахопліваецца на SO .

6
дададзена
@Starless Я абнавіў адказ з больш тлумачэннем.
дададзена аўтар user4815162342, крыніца
Дзякуй за адказ, ён сапраўды працаваў для мяне. Я выкарыстаў другі метад. Ці можаце вы растлумачыць, што вы маеце на ўвазе пад «канчатковыя замыкання ставяцца да адной і той жа вочку ф», а таксама можаце вы растлумачыць, першы метад.
дададзена аўтар Starless, крыніца

Адзін лайнер:

compose = lambda *F: reduce(lambda f, g: lambda x: f(g(x)), F)

Прыклад выкарыстання:

f1 = lambda x: x+3
f2 = lambda x: x*2
f3 = lambda x: x-1
g = compose(f1, f2, f3)
assert(g(7) == 15)
5
дададзена

Вы таксама можаце стварыць масіў функцый і выкарыстання паменшыць:

def f1(x): return x+1
def f2(x): return x+2
def f3(x): return x+3

x = 5

# Will print f3(f2(f1(x)))
print reduce(lambda acc, x: x(acc), [f1, f2, f3], x)

# As a function:
def compose(*funcs):
    return lambda x: reduce(lambda acc, f: f(acc), funcs, x)

f = compose(f1, f2, f3)
2
дададзена

Адказ тыкаць гэта добра, але вы таксама можаце выкарыстоўваць функцыянал пакет, які пастаўляецца з метадам уводу паведамленні.

2
дададзена
FWIK, падтрымлівае functional.compose толькі два аргументу.
дададзена аўтар georg, крыніца
@Starless Яна мае шмат рэцэптаў. Як і любы пісьменнік, вы павінны прачытаць, каб палепшыць тое, што вы пішаце.
дададзена аўтар Marcin, крыніца
@ Thg435 Ён мае рэцэпты для мульты-кампазіт.
дададзена аўтар Marcin, крыніца
Я шукаў у ажыццяўленні аднаго сябе
дададзена аўтар Starless, крыніца

Найбольш надзейным ўкараненне я знайшоў у бібліятэцы 3 партыі Toolz </код > . <�Код> Compose функцыі з гэтай бібліятэкі таксама займаецца на радок дакументацыі складу функцый.

зыходны код знаходзіцца ў вольным доступе. Ніжэй прыведзены просты прыклад выкарыстання.

from toolz import compose

def f(x):
    return x+1

def g(x):
    return x*2

def h(x):
    return x+3

res = compose(f, g, h)(5)  # 17
0
дададзена

Гэта мая версія

def compose(*fargs):
    def inner(arg):
        if not arg:
            raise ValueError("Invalid argument")
        if not all([callable(f) for f in fargs]):
            raise TypeError("Function is not callable")
        return reduce(lambda arg, func: func(arg), fargs, arg)
    return inner

Прыклад таго, як ён выкарыстоўваецца

def calcMean(iterable):
    return sum(iterable)/len(iterable)


def formatMean(mean):
    return round(float(mean), 2)


def adder(val, value):
    return val + value


def isEven(val):
    return val % 2 == 0

if __name__ == '__main__':
    # Ex1

    rand_range = [random.randint(0, 10000) for x in range(0, 10000)]

    isRandIntEven = compose(calcMean, formatMean,
                            partial(adder, value=0), math.floor.__call__, isEven)

    print(isRandIntEven(rand_range))
0
дададзена