在python中组合函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16739290/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 23:30:22  来源:igfitidea点击:

Composing functions in python

pythonfunctional-programmingcompositionfunction-composition

提问by Starless

I have an array of functions and I'm trying to produce one function which consists of the composition of the elements in my array. My approach is:

我有一个函数数组,我正在尝试生成一个函数,该函数由数组中元素的组合组成。我的做法是:

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

This method doesn't seems to be working, help will be appreciated.

此方法似乎不起作用,将不胜感激。

(I'm reversing the list because this is the order of composition I want the functions to be)

(我正在颠倒列表,因为这是我希望函数的组合顺序)

采纳答案by user4815162342

It doesn't work because all the anonymous functions you create in the loop refer to the same loop variable and therefore share its final value.

它不起作用,因为您在循环中创建的所有匿名函数都引用同一个循环变量,因此共享其最终值。

As a quick fix, you can replace the assignment with:

作为快速修复,您可以将分配替换为:

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

Or, you can return the lambda from a function:

或者,您可以从函数返回 lambda:

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

To understand what's going on, try this experiment:

要了解发生了什么,请尝试以下实验:

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

This result surprises many people, who expect the result to be [0, 1, 2, ...]. However, all the lambdas point to the same nvariable, and all refer to its final value, which is 9. In your case, all the versions of finalwhich are supposed to nest end up referring to the same fand, even worse, to the same final.

这个结果让很多人感到惊讶,他们预计结果会是[0, 1, 2, ...]。然而,所有的 lambdas 都指向同一个n变量,并且都指向它的最终值,即 9。在你的例子中,所有final应该嵌套的版本最终都指向相同的f,更糟糕的是,指向相同的final.

The topic of lambdas and for loops in Python has been already covered on SO.

Python 中的 lambdas 和 for 循环主题已经在 SO 上介绍过

回答by poke

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

Example:

例子:

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

回答by Imanol Luengo

You can also create an array of functions and use reduce:

您还可以创建一个函数数组并使用 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)

回答by Suor

The easiest approach would be first to write a composition of 2 functions:

最简单的方法是首先编写 2 个函数的组合:

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

And then use reduceto compose more functions:

然后reduce用来组合更多的函数:

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

Or you can use some library, which already contains composefunction.

或者你可以使用一些已经包含compose函数的

回答by Aaron Hall

Recursive implementation

递归实现

Here's a fairly elegant recursive implementation, which uses features of Python 3 for clarity:

这是一个相当优雅的递归实现,为了清晰起见,它使用了 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 compatible version:

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

This is an earlier version which uses lazy evaluation of the recursion:

这是使用递归延迟评估的早期版本:

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

Both would seem to make a new tuple and dict of arguments each recursive call.

两者似乎都会为每次递归调用创建一个新的元组和参数字典。

Comparison of all suggestions:

所有建议的比较:

Let's test some of these implementations and determine which is most performant, first some single argument functions (Thank you poke):

让我们测试其中的一些实现并确定哪个是性能最好的,首先是一些单参数函数(谢谢戳):

def square(x):
    return x ** 2

def increment(x):
    return x + 1

def half(x):
    return x / 2

Here's our implementations, I suspect my iterative version is the second most efficient (manual compose will naturally be fastest), but that may be in part due to it sidestepping the difficulty of passing any number of arguments or keyword arguments between functions - in most cases we'll only see the trivial one argument being passed.

这是我们的实现,我怀疑我的迭代版本是第二高效的(手动组合自然是最快的),但这可能部分是因为它避免了在函数之间传递任意数量的参数或关键字参数的困难 - 在大多数情况下我们只会看到传递的一个微不足道的参数。

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)

And to test these:

并测试这些:

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

Results

结果

And we get the following output (same magnitude and proportion in Python 2 and 3):

我们得到以下输出(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

And my expectations were confirmed: the fastest is of course, manual function composition followed by the iterative implementation. The lazy recursive version is much slower - likely since a new stack frame is created by each function call and a new tuple of functions is created for each function.

我的期望得到了证实:最快的当然是手动函数组合,然后是迭代实现。惰性递归版本要慢得多 - 可能是因为每个函数调用都会创建一个新的堆栈帧,并且为每个函数创建一个新的函数元组。

For a better and perhaps more realistic comparison, if you remove **kwargsand change *argsto argin the functions, the ones that used them will be more performant, and we can better compare apples to apples - here, aside from manual composition, reduce_compose1 wins followed by the strict_recursive_compose:

为了更好且可能更现实的比较,如果您在函数中删除**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

Functions with just one arg:

只有一个参数的函数:

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)

回答by Brett

One liner:

一个班轮:

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

Example usage:

用法示例:

f1 = lambda x: x+3
f2 = lambda x: x*2
f3 = lambda x: x-1
g = compose(f1, f2, f3)
assert(g(7) == 15)

回答by CasualCoder3

This is my version

这是我的版本

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

An example of how it's used

它是如何使用的一个例子

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

回答by jpp

The most reliable implementation I have found is in the 3rd party library toolz. The composefunction from this library also deals with docstring for the composition of functions.

我发现的最可靠的实现是在 3rd party library 中toolz。该compose库中的函数还处理用于组合函数的文档字符串。

The source codeis freely available. Below is a simple example of usage.

源代码是免费提供的。下面是一个简单的用法示例。

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

回答by Robert Vanden Eynde

pip install funcoperatorsis another library to implement it that allows infix notation:

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/

pip 安装 funcoperators https://pypi.org/project/funcoperators/

Disclaimer: I'm the creator of the module

免责声明:我是模块的创建者

回答by Роман Бутов

More general solution of Imanol Luengofrom my point of view (python notebook example):

从我的角度来看,Imanol Luengo 的更通用解决方案(python 笔记本示例):

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'}