Better Function Composition in Python

▼魔方 西西 提交于 2019-12-10 12:37:25

问题


I work in Python. Recently, I discovered a wonderful little package called fn. I've been using it for function composition.

For example, instead of:

baz(bar(foo(x))))

with fn, you can write:

(F() >> foo >> bar >> baz)(x) .

When I saw this, I immediately thought of Clojure:

(-> x foo bar baz) .

But notice how, in Clojure, the input is on the left. I wonder if this possible in python/fn.


回答1:


You can't replicate the exact syntax, but you can make something similar:

def f(*args):
    result = args[0]

    for func in args[1:]:
        result = func(result)

    return result

Seems to work:

>>> f('a test', reversed, sorted, ''.join)
' aestt'



回答2:


You can't get that exact syntax, although you can get something like F(x)(foo, bar, baz). Here's a simple example:

class F(object):
    def __init__(self, arg):
        self.arg = arg

    def __call__(self, *funcs):
        arg = self.arg
        for f in funcs:
            arg = f(arg)
        return arg

def a(x):
    return x+2
def b(x):
    return x**2
def c(x):
    return 3*x

>>> F(2)(a, b, c)
48
>>> F(2)(c, b, a)
38

This is a bit different from Blender's answer since it stores the argument, which can later be re-used with different functions.

This is sort of like the opposite of normal function application: instead of specifying the function up front and leaving some arguments to be specified later, you specify the argument and leave the function(s) to be specified later. It's an interesting toy but it's hard to think why you'd really want this.




回答3:


If you want to use fn, with a little hack you can get a bit closer to Clojure syntax:

>>> def r(x): return lambda: x
>>> (F() >> r(x) >> foo >> bar >> baz)()

See how I added another function at the beginning of the composition chain that will just return x when called. The problem with this is that you still have to call your composed function, just without any arguments.

I think @Blender's answer is your best bet trying to emulate Clojure's thread function in Python.




回答4:


I came up with this

def _composition(arg, *funcs_and_args):
    """
    _composition(
        [1,2,3], 
        (filter, lambda x: x % 2 == 1), 
        (map, lambda x: x+3)
    )
    #=> [4, 6]
    """
    for func_and_args in funcs_and_args:
        func, *b = func_and_args
        arg = func(*b, arg)
    return(arg)



回答5:


This seems to work for simple input. Not sure it is worth the effort for complex input, e.g., ((42, 'spam'), {'spam': 42}).

def compose(function, *functions):
    return function if not functions else \
            lambda *args, **kwargs: function(compose(*functions)(*args, **kwargs))

def rcompose(*functions):
    return compose(*reversed(functions))

def postfix(arg, *functions):
    return rcompose(*functions)(arg)

Example:

>>> postfix(1, str, len, hex)
'0x1'
>>> postfix(1, hex, len)
3



回答6:


My compose function that returns a function

def compose(*args):
    length = len(args)
    def _composeInner(lastResult, index):
        if ((length - 1) < index):
            return lastResult
        return _composeInner(args[index](lastResult), index + 1)

    return (lambda x: _composeInner(x, 0))

Usage:

fn = compose(
        lambda x: x * 2,
        lambda x: x + 2,
        lambda x: x + 1,
        lambda x: x / 3
    )

result = fn(6) # -> 5


来源:https://stackoverflow.com/questions/17264406/better-function-composition-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!