37

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

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

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

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

4

15 回答 15

42

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

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

然后reduce用来组合更多的功能:

import functools

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

或者你可以使用一些已经包含compose功能的库。

于 2014-06-04T20:43:36.007 回答
20
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
于 2013-05-24T16:17:49.983 回答
19

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

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

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

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

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, ...]。但是,所有的 lambdas 都指向同一个n变量,并且都指向它的最终值,即 9。在您的情况下,final应该嵌套的所有版本最终都指向相同的变量f,甚至更糟的是指向相同的变量final.

Python 中的 lambdas 和 for 循环的主题已经在 SO 上进行了介绍。

于 2013-05-24T16:16:36.933 回答
15

一个班轮:

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)
于 2016-06-01T00:52:19.330 回答
15

递归实现

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

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

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

只有一个参数的函数:

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)
于 2016-01-11T02:25:08.133 回答
6

您还可以创建一个函数数组并使用 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)
于 2013-05-24T16:30:25.347 回答
6

我发现的最可靠的实现是在 3rd party librarytoolz中。该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
于 2018-04-28T13:14:55.740 回答
4

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/

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

于 2018-12-22T13:57:36.903 回答
2

假设您有以下功能:

def square(x): 
    return x**2

def inc(x): 
    return x+1

def half(x): 
    return x/2

定义一个compose函数如下:

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/

于 2019-12-08T05:43:18.713 回答
1

我从 GeeksforGeeks在这里找到了 Python 3 的这段代码。不确定它的效率如何,但它很容易理解。

# importing reduce() from functools 
from functools import reduce

# composite_function accepts N 
# number of function as an 
# argument and then compose them 
def composite_function(*func): 
    
    def compose(f, g): 
        return lambda x : f(g(x)) 
            
    return reduce(compose, func, lambda x : x) 

# Function to add 2 
def add(x): 
    return x + 2

# Function to multiply 2 
def multiply(x): 
    return x * 2

# Function to subtract 2 
def subtract(x): 
    return x - 1

# Here add_subtract_multiply will 
# store lambda x : multiply(subtract(add(x))) 
add_subtract_multiply = composite_function(multiply, 
                                        subtract, 
                                        add) 

print("Adding 2 to 5, then subtracting 1 and multiplying the result with 2: ", 
    add_subtract_multiply(5)) 

您可以继续向 composite_functions 添加更多功能,例如:

print(composite_function(multiply, add, subtract, multiply,subtract, add)(5))
于 2021-01-29T09:37:36.200 回答
1

您可以使用funcy.

安装:

pip install funcy

然后你可以使用composeorrcompose如下:

from funcy import compose, rcompose

def inc(x): return x + 1
def double(x): return x + x
def tripple(x): return x + x + x

print(compose(tripple, double, inc)(1)) # 12
print(rcompose(inc, double, tripple)(1)) # 12
于 2021-12-02T08:23:58.320 回答
1

由于可读性/简单性,我更喜欢这个

from functools import reduce

def compose(*fs):
   apply = lambda arg, f: f(arg)
   composition = lambda x: reduce(apply, [x, *fs])
   return composition

pipe = compose(a, b, c)首先应用 a,然后是 b,然后是 c。

关于可维护性(调试),我认为实际上这个是最容易使用的:

def compose(*fs):
    def composition(x):
        for f in fs:
            x = f(x)
        return x
    return composition
于 2020-08-15T01:01:48.683 回答
0

非常好的问题,但答案肯定是不必要的复杂。只是:

def compose(*funs):
    return (lambda x:
        x if len(funs) == 0
         else compose(*funs[:-1])(funs[-1](x)))
于 2021-05-11T16:13:45.210 回答
0

从我的角度来看, 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'}
于 2019-06-27T18:08:40.023 回答
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))
于 2017-12-04T23:29:33.660 回答