\" macro which inserts each expression recursively as the first argument of the next expression." />
开发者

Clojure style function "threading" in Python

开发者 https://www.devze.com 2023-02-09 23:57 出处:网络
Clojure has a \"->\" macro which inserts each expression recursively as the first argument of the next expression.

Clojure has a "->" macro which inserts each expression recursively as the first argument of the next expression.

This means that I could write:

(-> arg f1 f2 f3)

and it behaves like (s开发者_如何学Chell piping):

f3(f2(f1(arg)))

I would like to do this in Python; however, searching seems to be a nightmare! I couldn't search for "->", and neither could I search for Python function threading!

Is there a way to overload, say, the | operator so that I could write this in Python?

arg | f1 | f2 | f3

Thanks!


Or possibly use the reduce function in the following way:

reduce(lambda x,f : f(x), [f1,f2,f3], arg)


You can easily implement something like this yourself.

def compose(current_value, *args):
    for func in args:
        current_value = func(current_value)
    return current_value

def double(n):
    return 2*n

print compose(5, double, double) # prints 20


Or try https://mdk.fr/blog/pipe-infix-syntax-for-python.html A module that provide a syntax like :

  fib() | take_while(lambda x: x < 1000000)
        | where(lambda x: x % 2)
        | select(lambda x: x * x)
        | sum()


Building on Howard's solution:

def T(*args):
  return reduce(lambda l, r: r(l), args)

def dbl(n):
    return 2*n

T(5,dbl,dbl)
#=> 20

T(5,dbl,dbl,lambda x: 3*x)
#=> 60


While I sympathize with the desire to create cool new language constructs (à la Lisp macros), it is not really the Python philosophy to do this:

>>> import this
[...]
There should be one-- and preferably only one --obvious way to do it.

But as the respondents have said, you can do your function chaining in a variety of ways. Here is one that's perhaps more explicitly Lisp-like, if that suits your fancy:

a = lambda x: x*2
b = lambda x: x+1

def chain(first, *args):
    if len(args) == 0:
        return first
    else:
        return first(chain(*args))

print chain(b, a, 1)


A little late to the party, but here's a cleaner method, imo. Will suit most FP needs.

def stream(*args):
    return reduce(lambda a, t: t[0](t[1], a), args[1:], args[0])

A basic map, filter, reduce:

>>> my_list = [1, 2, 3, 4, 5]
>>> stream(my_list, 
...    (map,    lambda x: x ** 2),
...    (filter, lambda x: x < 20),
...    (reduce, lambda a, x: a + x))
30


There's a thread function in the pytoolz library (actually there are two; they do slightly different things on functions of multiple arguments).

There's also a cython implementation of the pytoolz library called cytoolz which is probably more efficient. It can be installed using pip.


I believe that a quite "Pythonic" approach to this would be to thread functions like this:

thread_functions(my_arg)(func1, func2, ...)

Or, if you have multiple initial arguments:

thread_functions(arg1, arg2, ...)(func1, func2, ...)

To implement the above, one could do:

def thread_functions(*init_args):
    def execute_functions(*functions_list):
        x = functions_list[0](*init_args)

        for func in functions_list[1:]:
            try:
                x = func(*x)
            except TypeError:
                x = func(x)

        return x

return execute_functions

The function thread_functions receives the initial set of arguments (that can be many) and returns the function execute_functions. When called, execute_functions receives a set of functions to iterate on, appling the result of the previous iteration as the arguments to the current function. It also handles both iterable and non iterable arguments.

In this GitHub Gist I left some other explanations and implemented some examples to better demonstrate the use cases.


No, there is not (at least sanely). Nor would you want to. Why not just write f3(f2(f1(arg)))? Or better yet, model your problem in a way that doesn't require recursion.

You might be able to overload | by wrapping expressions in a class and defining __or__ in that class, but please, for the love of Guido, don't do that.

You could also do what btilly wrote, but I wouldn't recommend that either. Work within what the language provides you.

0

精彩评论

暂无评论...
验证码 换一张
取 消