【问题标题】:Composing functions in python在python中组合函数
【发布时间】:2013-05-20 07:26:27
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: python functional-programming composition function-composition


    【解决方案1】:

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

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

    然后用reduce组合出更多函数:

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

    或者你可以使用some library,它已经包含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 个功能的最常见情况,我的成本为零。
    • reduce是python3中的functools.reduce
    • 请注意compose(a,b,c) 将产生以下顺序a(b(c(input)))
    【解决方案2】:
    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
    

    【讨论】:

    • 您能否展示如何(/甚至可能)添加聚合步骤 - 假设链式函数正在对集合进行操作?
    • @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)) - 你也可以只使用一次 map 组合函数:compose(sum, lambda x: sorted(x, reverse=True)[:3], lambda x: map(compose(lambda y: y * 3, lambda y: y + 5), x))。因此,如果您将它们命名好,它可能看起来像这样:compose(sum, top3, lambda x: map(compose(times3, plus5), x))。您也可以使用 functools.partial 摆脱 lambda
    【解决方案3】:

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

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

    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 版本最终都指向相同的 @987654328 @,更糟糕的是,同样的final

    关于 Python 中的 lambda 和 for 循环的话题一直是already covered on SO

    【讨论】:

    • 感谢您的回答,它确实对我有用。我使用了第二种方法。你能解释一下“最终闭包引用同一个 f 单元”是什么意思吗?你能解释一下第一种方法吗?
    • 这是一个有趣的选择。将l 替换为l = [lambda x=n: x for n in range(10)] 这会产生[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],正如人们所期望的那样。
    • @RussAbbott 这就是答案开头附近提出的“快速修复”的要点。在该模式中,约定是将关键字命名为与您正在捕获的变量相同的名称,例如lambda n=n: ....
    【解决方案4】:

    一个班轮:

    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)
    

    【讨论】:

      【解决方案5】:

      递归实现

      这是一个相当优雅的递归实现,为了清晰起见,它使用了 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并将函数中的*args更改为arg,使用它们的函数会更好,我们可以更好地比较苹果和苹果 -在这里,除了手动组合之外,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)
      

      【讨论】:

        【解决方案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)
        

        【讨论】:

        • 您能否展示如何(/甚至可能)添加聚合步骤 - 假设链式函数正在对集合进行操作?
        【解决方案7】:

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

        source code 是免费提供的。下面是一个简单的用法示例。

        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
        

        【讨论】:

          【解决方案8】:

          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/

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

          【讨论】:

            【解决方案9】:

            假设你有以下函数:

            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. inc (= 101)
            3. inc (= 102)
            4. 一半(= 51)

            改编自https://mathieularose.com/function-composition-in-python/

            【讨论】:

            • 这对我来说很有趣,因为程序执行 - 但是(在 python 3 中)在 print(compose(10)) 上我得到:... at 0x000002E51BF3FDC0> 我不确定我需要做什么才能获得价值。
            【解决方案10】:

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

            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
            

            【讨论】:

              【解决方案11】:

              我从 GeeksforGeeks here 找到了这段代码,用于 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))
              

              【讨论】:

                【解决方案12】:

                您可以使用funcy

                安装:

                pip install funcy
                

                那么你可以使用composercompose如下:

                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
                

                【讨论】:

                  【解决方案13】:

                  这是我的版本

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

                  【讨论】:

                    【解决方案14】:

                    从我的角度来看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'}
                    

                    【讨论】:

                      【解决方案15】:

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

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

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2019-08-17
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2020-07-24
                        相关资源
                        最近更新 更多