用python编写函数

用python编写函数,python,functional-programming,composition,function-composition,Python,Functional Programming,Composition,Function Composition,我有一个函数数组,我试图生成一个函数,它由数组中的元素组成。 我的做法是: 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 此方法似乎不起作用,请给予帮助 (我正在反转列

我有一个函数数组,我试图生成一个函数,它由数组中的元素组成。 我的做法是:

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
此方法似乎不起作用,请给予帮助


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

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

作为快速修复,您可以使用以下内容替换分配:

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,…]
。但是,所有lambda都指向相同的
n
变量,并且都指向其最终值,即9。在您的例子中,所有本应嵌套的
final
版本最终都指向相同的
f
,更糟糕的是,指向相同的
final

Python中lambdas和for循环的主题已经被讨论过

例如:

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

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

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

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

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

def inc(x): 
    return x+1

def half(x): 
    return x/2
然后使用
reduce
组合更多函数:

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

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

def inc(x): 
    return x+1

def half(x): 
    return x/2
或者您可以使用,它已经包含函数。

递归实现 下面是一个相当优雅的递归实现,为了清晰起见,它使用了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
在每次递归调用中,这两种方法似乎都会生成一个新的元组和dict参数

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

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

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和Python 3中的大小和比例相同):

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

为了更好、更现实地进行比较,如果您删除函数中的
**kwargs
并将
*args
更改为
arg
,则使用它们的函数的性能会更高,我们可以更好地对苹果进行比较-除了手动合成,减少合成1,然后是严格的递归合成:

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)
一艘班轮:

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)
这是我的版本

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

我发现最可靠的实现是在第三方库中。此库中的
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

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安装运算符

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

从我的角度来看更通用的解决方案():


假设您具有以下功能:

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

def compose(*fs):
    return functools.reduce(compose2, fs)
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
它按定义的顺序按程序执行函数:

  • 正方形(=100)
  • 公司(=101)
  • 公司(=102)
  • 一半(=51)

  • 改编自。

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

    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
    

    我从Geeksforgeks为Python3找到了这段代码。不确定它的效率有多高,但很容易理解

    # 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)) 
    
    您可以继续向复合函数添加更多函数,例如:

    print(composite_function(multiply, add, subtract, multiply,subtract, add)(5))
    

    这是一个很好的问题,但答案肯定过于复杂。只是:

    def compose(*funs):
        return (lambda x:
            x if len(funs) == 0
             else compose(*funs[:-1])(funs[-1](x)))
    

    谢谢你的回答,它确实对我有用。我使用了第二种方法。你能解释一下“最终闭包引用同一个f单元”是什么意思吗?你能解释一下第一种方法吗?这里有一个有趣的选择。将
    l
    替换为
    l=[lambda x=n:x表示范围(10)]
    这会产生
    [0,1,2,3,4,5,6,7,8,9]
    。@RussAbbott这就是在答案开头提出的“快速修复”的要点。在这种模式中,约定是将关键字命名为与您捕获的变量相同的名称,例如,
    lambda n=n:…
    。您能否说明(/甚至可能)如何添加聚合步骤-假定链接函数在集合上运行?@javadba我不确定您的意思。你能举一个你想做的例子吗?考虑函数可能是:<代码>(添加5到x,3乘,*找到前3 *,*和*)< /代码>。“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))
    –您也可以使用组合函数
    map
    一次:
    compose(求和,lambda x:sorted(x,reverse=True)[:3],lambda x:map(compose(lambda y:y*3,lambda y:y+5),x))