web-dev-qa-db-ja.com

pythonで関数を作成する

関数の配列があり、配列内の要素の構成で構成される1つの関数を作成しようとしています。私のアプローチは:

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

この方法は機能していないようです。助けていただければ幸いです。

(これは私が関数にしたい構成の順序なので、リストを逆にします)

28
Starless

ループで作成したすべての無名関数は同じループ変数を参照し、そのため最終的な値を共有するため、機能しません。

簡単な修正として、割り当てを次のように置き換えることができます。

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, ...]であると期待している多くの人々を驚かせます。ただし、すべてのラムダは同じn変数を指し、すべてがその最終値である9を参照します。あなたのケースでは、ネストすることになっているfinalのすべてのバージョンが同じfを参照し、さらに悪いことに、同じfinalに。

Python=でのラムダとforループのトピックは すでにSOでカバーされています です。

11
user4815162342

最も簡単なアプローチは、最初に2つの関数の合成を記述することです。

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

次に、reduceを使用して、さらに関数を作成します。

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

または、すでに compose 関数が含まれている some library を使用できます。

28
Suor
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
15
poke

一発ギャグ:

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)
9
Brett

再帰的な実装

これはかなりエレガントな再帰的な実装で、Python 3の機能を使用してわかりやすくしています。

def strict_compose(*funcs):
    *funcs, penultimate, last = funcs
    if funcs:
        penultimate = strict_compose(*funcs, penultimate)
    return lambda *args, **kwargs: penultimate(last(*args, **kwargs))

Python 2互換バージョン:

def strict_compose2(*funcs):
    if len(funcs) > 2:
        penultimate = strict_compose2(*funcs[:-1])
    else:
        penultimate = funcs[-2]
    return lambda *args, **kwargs: penultimate(funcs[-1](*args, **kwargs))

これは、再帰の遅延評価を使用する以前のバージョンです。

def lazy_recursive_compose(*funcs):
    def inner(*args, _funcs=funcs, **kwargs):
        if len(_funcs) > 1:
            return inner(_funcs[-1](*args, **kwargs), _funcs=_funcs[:-1])
        else:
            return _funcs[0](*args, **kwargs)
    return inner

どちらも、新しいタプルを作成し、再帰呼び出しごとに引数の口述を行います。

すべての提案の比較:

これらの実装のいくつかをテストして、最もパフォーマンスの高いものを決定しましょう。最初にいくつかの単一引数関数です(ありがとうポーク)。

def square(x):
    return x ** 2

def increment(x):
    return x + 1

def half(x):
    return x / 2

ここに私たちの実装があります。反復バージョンが2番目に効率的であると思います(手動の作成は当然最速です)が、これは、関数間で任意の数の引数またはキーワード引数を渡すことの難しさを回避するためである可能性があります-ほとんどの場合単純な引数が渡されるだけです。

from functools import reduce

def strict_recursive_compose(*funcs):
    *funcs, penultimate, last = funcs
    if funcs:
        penultimate = strict_recursive_compose(*funcs, penultimate)
    return lambda *args, **kwargs: penultimate(last(*args, **kwargs))

def strict_recursive_compose2(*funcs):
    if len(funcs) > 2:
        penultimate = strict_recursive_compose2(*funcs[:-1])
    else:
        penultimate = funcs[-2]
    return lambda *args, **kwargs: penultimate(funcs[-1](*args, **kwargs))

def lazy_recursive_compose(*funcs):
    def inner(*args, _funcs=funcs, **kwargs):
        if len(_funcs) > 1:
            return inner(_funcs[-1](*args, **kwargs), _funcs=_funcs[:-1])
        else:
            return _funcs[0](*args, **kwargs)
    return inner

def iterative_compose(*functions):
    """my implementation, only accepts one argument."""
    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

def manual_compose(n):
    return square(increment(half(n)))

composes = (strict_recursive_compose, strict_recursive_compose2, 
            lazy_recursive_compose, iterative_compose, 
            reduce_compose1, reduce_compose2)

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

結果

そして、次の出力が得られます(Python 2と3で同じ大きさと比率):

manual compose 0.4963762479601428 12.25
strict_recursive_compose 0.6564744340721518 12.25
strict_recursive_compose2 0.7216697579715401 12.25
lazy_recursive_compose 1.260614730999805 12.25
iterative_compose 0.614982972969301 12.25
reduce_compose1 0.6768529079854488 12.25
reduce_compose2 0.9890829260693863 12.25

そして、私の期待が確認されました。最も速いのは、もちろん、手動で関数を作成してから、繰り返し実装することです。遅延再帰バージョンははるかに遅くなります。おそらく、新しいスタックフレームが各関数呼び出しによって作成され、関数の新しいタプルが関数ごとに作成されるためです。

より適切でおそらくより現実的な比較のために、関数で**kwargsを削除して*argsargに変更すると、それらを使用したもののパフォーマンスが向上し、より適切に比較できますりんごからりんご-ここでは、手動構成を除いて、reduce_compose1が勝利し、その後にstrict_recursive_composeが続きます。

manual compose 0.443808660027571 12.25
strict_recursive_compose 0.5409777010791004 12.25
strict_recursive_compose2 0.5698030130006373 12.25
lazy_recursive_compose 1.0381018499610946 12.25
iterative_compose 0.619289995986037 12.25
reduce_compose1 0.49532539502251893 12.25
reduce_compose2 0.9633988010464236 12.25

引数が1つだけの関数:

def strict_recursive_compose(*funcs):
    *funcs, penultimate, last = funcs
    if funcs:
        penultimate = strict_recursive_compose(*funcs, penultimate)
    return lambda arg: penultimate(last(arg))

def strict_recursive_compose2(*funcs):
    if len(funcs) > 2:
        penultimate = strict_recursive_compose2(*funcs[:-1])
    else:
        penultimate = funcs[-2]
    return lambda arg: penultimate(funcs[-1](arg))

def lazy_recursive_compose(*funcs):
    def inner(arg, _funcs=funcs):
        if len(_funcs) > 1:
            return inner(_funcs[-1](arg), _funcs=_funcs[:-1])
        else:
            return _funcs[0](arg)
    return inner

def iterative_compose(*functions):
    """my implementation, only accepts one argument."""
    def inner(arg):
        for f in reversed(functions):
            arg = f(arg)
        return arg
    return inner

def _compose2(f, g):
    return lambda arg: f(g(arg))

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)
9
Aaron Hall

関数の配列を作成し、reduceを使用することもできます。

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)
4
Imanol Luengo

pip install funcoperatorsは、インフィックス表記を可能にする、それを実装する別のライブラリです。

from funcoperators import compose

# display = lambda x: hex(ord(list(x)))
display = hex *compose* ord *compose* list

# also works as a function
display = compose(hex, ord, list)

pip install funcoperators https://pypi.org/project/funcoperators/

免責事項:私はモジュールの作成者です

3

私が見つけた最も信頼性の高い実装は、サードパーティのライブラリ toolz にあります。このライブラリのcompose関数は、関数の構成のためのdocstringも扱います。

ソースコード は自由に利用できます。以下は簡単な使用例です。

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
3
jpp

Pokeの答えは良いですが、composeメソッドに付属するfunctionalパッケージを使用することもできます。

2
Marcin

次の関数があるとします。

def square(x): 
    return x**2

def inc(x): 
    return x+1

def half(x): 
    return x/2

次のように構成関数を定義します。

import functools

def compose(*functions):
    return functools.reduce(lambda f, g: lambda x: g(f(x)),
                            functions,
                            lambda x: x)

使用法:

composed = compose(square, inc, inc, half)
compose(10)
>>> 51.0

これは、定義された順序で関数を手続き的に実行します。

  1. 正方形(= 100)
  2. 株式会社(= 101)
  3. 株式会社(= 102)
  4. 半分(= 51)

https://mathieularose.com/function-composition-in-python/ から適応。

0
Raimi bin Karim

これは私のバージョンです

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
CasualCoder3

私の観点から Imanol Luengo のより一般的な解決策( python notebook example ):

from functools import reduce
from functools import partial

def f(*argv, **kwargs):
  print('f: {} {}'.format(argv, kwargs))
  return argv, kwargs

def g(*argv, **kwargs):
  print('g: {} {}'.format(argv, kwargs))
  return argv, kwargs

def compose(fs, *argv, **kwargs):
  return reduce(lambda x, y: y(*x[0], **x[1]), fs, (argv, kwargs))

h = partial(compose, [f, g])
h('value', key='value')
output:
f: ('value',) {'key': 'value'}
g: ('value',) {'key': 'value'}

m = partial(compose, [h, f, g])
m('value', key='value')
output:
f: ('value',) {'key': 'value'}
g: ('value',) {'key': 'value'}
f: ('value',) {'key': 'value'}
g: ('value',) {'key': 'value'}