我有一个函数数组,我试图产生一个由数组中元素组成的函数。
我的方法是:
1
2
3
4
5
6
7
8def 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
此方法似乎无效,将不胜感激。
(我正在反转列表,因为这是我希望函数成为的组合顺序)
最简单的方法是首先编写2个函数的组合:
1
2def compose2(f, g):
return lambda *a, **kw: f(g(*a, **kw))
然后使用reduce组合更多功能:
1
2def compose(*fs):
return reduce(compose2, fs)
或者,您可以使用一些已经包含compose函数的库。
这将为fs中的每个函数创建一个阴影函数。我不知道Python中有多少函数会占用大量资源,但这似乎很浪费。相反,请参阅Imanol Luengo的其他解决方案:def compose(*funcs): return lambda x: reduce(lambda acc, f: f(acc), funcs, x)(stackoverflow.com/a/16739663/216138)
您可以进行测试,但是您的解决方案可能会变慢。对于2函数的最常见情况,我的成本为零。
1
2
3
4
5
6def compose (*functions):
def inner(arg):
for f in reversed(functions):
arg = f(arg)
return arg
return inner
例:
1
2
3
4
5
6
7
8
9
10>>> 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
您是否可以显示如何(甚至有可能)添加一个聚合步骤-假设链接函数在集合上运行?
@javadba我不确定您的意思。您能举一个您想做的事的例子吗?
考虑的功能可能是:(add 5 to x, mult by 3, *find top 3*, *sum*)。" top3"和" sum"是我不知道如何插入到组合中的聚合。
@javadba您肯定可以做到这一点,尽管我会说这看起来有点复杂:compose(sum, lambda x: sorted(x, reverse=True)[:3], lambda x: map(lambda y: y * 3, x), lambda x: map(lambda y: y + 5, x)) –您也可以只使用组合函数compose(sum, lambda x: sorted(x, reverse=True)[:3], lambda x: map(compose(lambda y: y * 3, lambda y: y + 5), x))一次map。因此,如果您给它们起好名字,它可能看起来像这样:compose(sum, top3, lambda x: map(compose(times3, plus5), x))。您还可以使用functools.partial摆脱该lambda。
它不起作用,因为您在循环中创建的所有匿名函数都引用相同的循环变量,因此共享其最终值。
作为快速解决方案,您可以将分配替换为:
1final = lambda x, f=f, final=final: f(final(x))
或者,您可以从函数返回lambda:
1
2
3
4def wrap(accum, f):
return lambda x: f(accum(x))
...
final = wrap(final, f)
要了解发生了什么,请尝试以下实验:
1
2
3>>> 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, ...]。但是,所有的lambda都指向相同的n变量,并且都引用其最终值9。在您的情况下,所有应该嵌套的final版本最终都引用相同的,甚至更糟的是使用相同的final。
SO中已经讨论了lambda和for循环这一主题。
感谢您的回答,它确实对我有用。我用了第二种方法。您能否解释"最终闭包引用相同的F单元"是什么意思,也请您解释第一种方法。
这是一个有趣的选择。用l = [lambda x=n: x for n in range(10)]替换l这将产生[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],这可能与您期望的一样。
@RussAbbott多数民众赞成在答案开头附近提出的"快速修复"要点。在这种模式下,惯例是将关键字命名为与要捕获的变量相同的名称,例如lambda n=n: ...。
递归实施
这是一个相当优雅的递归实现,为了清楚起见,它使用了Python 3的功能:
1
2
3
4
5def strict_compose(*funcs):
*funcs, penultimate, last = funcs
if funcs:
penultimate = strict_compose(*funcs, penultimate)
return lambda *args, **kwargs: penultimate(last(*args, **kwargs))
Python 2兼容版本:
1
2
3
4
5
6def 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))
这是较早的版本,它使用递归的惰性求值:
1
2
3
4
5
6
7def 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
每次递归调用时,两者似乎都会创建一个新的元组和参数字典。
比较所有建议:
让我们测试一下其中的一些实现,并确定哪个性能最高,首先是一些单参数函数(谢谢戳):
1
2
3
4
5
6
7
8def square(x):
return x ** 2
def increment(x):
return x + 1
def half(x):
return x / 2
这是我们的实现,我怀疑我的迭代版本是第二高效的(手动撰写自然会最快),但这可能部分是由于它避免了在函数之间传递任意数量的参数或关键字参数的困难-在大多数情况下我们只会看到传递一个琐碎的参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40from 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)
并测试这些:
1
2
3
4
5
6
7
8
9
10
11
12
13
14import 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中的大小和比例相同):
1
2
3
4
5
6
7manual 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并将*args更改为arg,则使用它们的函数将更具性能,我们可以更好地将苹果与苹果进行比较-除了这里,从手动组合中,reduce_compose1获胜,然后是strict_recursive_compose:
1
2
3
4
5
6
7manual 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
仅带一个arg的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38def 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)
一班轮:
1compose = lambda *F: reduce(lambda f, g: lambda x: f(g(x)), F)
用法示例:
1
2
3
4
5f1 = lambda x: x+3
f2 = lambda x: x*2
f3 = lambda x: x-1
g = compose(f1, f2, f3)
assert(g(7) == 15)
您还可以创建函数数组并使用reduce:
1
2
3
4
5
6
7
8
9
10
11
12
13
14def 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)
您是否可以显示如何(甚至有可能)添加一个聚合步骤-假设链接函数在集合上运行?
我发现的最可靠的实现是在第三方库toolz中。该库中的compose函数还处理docstring以构成函数。
源代码是免费提供的。以下是用法的简单示例。
1
2
3
4
5
6
7
8
9
10
11
12from 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
pip install funcoperators是另一个实现它的库,该库允许使用中缀符号:
1
2
3
4
5
6
7from 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)
点安装funcoperators https://pypi.org/project/funcoperators/
免责声明:我是模块的创建者
假设您具有以下功能:
1
2
3
4
5
6
7
8def square(x):
return x**2
def inc(x):
return x+1
def half(x):
return x/2
定义一个compose函数,如下所示:
1
2
3
4
5
6import functools
def compose(*functions):
return functools.reduce(lambda f, g: lambda x: g(f(x)),
functions,
lambda x: x)
用法:
1
2
3composed = compose(square, inc, inc, half)
compose(10)
>>> 51.0
它以定义的顺序按程序执行功能:
平方(= 100)
inc(= 101)
inc(= 102)
一半(= 51)
改编自https://mathieularose.com/function-composition-in-python/。
从我的角度来看,Imanol Luengo的更一般的解决方案(python笔记本示例):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27from 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'}
这是我的版本
1
2
3
4
5
6
7
8def 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
用法示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24def 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))