python infix forward pipe
Asked Answered
W

2

6

I'm trying to implement a forward pipe functionality, like bash's | or R's recent %>%. I've seen this implementation https://mdk.fr/blog/pipe-infix-syntax-for-python.html, but this requires that we define in advance all the functions that might work with the pipe. In going for something completely general, here's what I've thought of so far.

This function applies its first argument to its second (a function)

def function_application(a,b):
    return b(a)

So for example, if we have a squaring function

def sq(s):
    return s**2

we could invoke that function in this cumbersome way function_application(5,sq). To get a step closer to a forward pipe, we want to use function_application with infix notation.

Drawing from this, we can define an Infix class so we can wrap functions in special characters such as |.

class Infix:
    def __init__(self, function):
        self.function = function
    def __ror__(self, other):
        return Infix(lambda x, self=self, other=other: self.function(other, x))
    def __or__(self, other):
        return self.function(other)

Now we can define our pipe which is simply the infix version of the function function_application,

p = Infix(function_application)

So we can do things like this

5 |p| sq
25

or

[1,2,3,8] |p| sum |p| sq
196

After that long-winded explanation, my question is if there is any way to override the limitations on valid function names. Here, I've named the pipe p, but is it possible to overload a non-alphanumeric character? Can I name a function > so my pipe is |>|?

Wizened answered 23/3, 2015 at 20:20 Comment(1)
Very interesting article you have referenced. However, I don't think it's possible to name a function with >.Ado
N
6

Quick answer:

You can't really use |>| in python, at the bare minimum you need | * > * | where * needs to be a identifier, number, string, or another expression.

Long answer:

Every line is a statement (simple or compound), a stmt can be a couple of things, among them an expression, an expression is the only construct that allows the use of or operator | and greater than comparison > (or all operators and comparisons for that matter < > <= >= | ^ & >> << - + % / //), every expression needs a left hand side and a right hand side, ultimatelly being in the form lhs op rhs, both left and right hand side could be another expression, but the exit case is the use of an primary (with the exception of unnary -, ~ and + that need just a rhs), the primary will boil down to an identifier, number or string, so, at the end of the day you are required to have an identifier [a-zA-Z_][a-zA-Z_0-9]* along side a |.

Have you considered a different approach, like one class that override the or operator instead of a infix class? I have a tiny library that does piping, might interest you

For reference, here is the full grammar:

https://docs.python.org/2/reference/grammar.html

Neldanelia answered 23/3, 2015 at 20:45 Comment(1)
I like the approach taken by your library. It's quite elegant.Ado
L
4

I was looking for a way to do this too. So I created a Python library called Pypework.

You just add a prefix such as f. to the beginning of each function call to make it pipeable. Then you can chain them together using the >> operator, like so:

"Lorem Ipsum" >> f.lowercase >> f.replace(" ", "_") # -> "lorem_ipsum"

Or across multiple lines if wrapped in parentheses, like so:

(
  "Lorem Ipsum"
    >> f.lowercase
    >> f.replace(" ", "_")
)

# -> "lorem_ipsum"
Lancelle answered 28/2, 2018 at 10:34 Comment(3)
Very cool, I wonder if you could do something similar to: subprocess.Process objects to manipulate stdin and stdout to replicate piping in shell scripts in python: run('grep', 'example', 'somefile.txt') >> run('some', 'other', 'command')Thapsus
This is a nice approach. Can you freely mix functions and methods?Uxorial
@Uxorial Thanks. I did start working on a version where you could mix functions and methods, yes. Though I can't remember how finished it was.Lancelle

© 2022 - 2024 — McMap. All rights reserved.