Introduce funnel operator i,e '|>' to allow for generator pipelines

So just to step back a bit. Let’s take 2 base cases for this, which are both available without any changes:

Setup
from functools import partial, Placeholder as _

class MethodCaller:
    def __init__(self, arg=None):
        self.arg = arg

    def __getattr__(self, name):
        assert self.arg is None
        return type(self)(name)

    def __call__(self, *args, **kwds):
        arg = self.arg
        assert arg is not None
        if type(arg) is str:
            return type(self)((arg, args, kwds))
        else:
            assert len(arg) == 3 and len(args) == 1 and not kwds
            return getattr(args[0], arg[0])(*arg[1], **arg[2])

MC = MethodCaller()


class P:
    def __init__(self, *args, **kwds):
        self.args = args
        self.kwds = kwds
    def __call__(self, func):
        return partial(func, *self.args, **self.kwds)
    __rmatmul__ = __ror__ = __rrshift__ = __call__

1. rpipe object

class rpipe:
    def __init__(self, obj):
        self.obj = obj
    def __iter__(self):
        return self.obj
    def __or__(self, func):
        if func is type(self):
            return self.obj
        return type(self)(func(self.obj))


rpipe(1) | MC.as_integer_ratio() | sum | format@P(_, '.2f') | rpipe
  1. Infix operator
# Implementation left out as quite cumbersome
1 |x| MC.as_integer_ratio() |x| sum |x| format@P(_, '.2f') |x| x


So there are 2 working solutions which are NOT utterly inconvenient and don’t require anything too sophisticated or unreasonable imports.

So what could be improvements to above and which of those improvements are sensible when seen in the light of how much complexity they add versus marginal convenience?

1. So say, implementation of pipe operator |> would make:

rpipe(1) | MC.as_integer_ratio() | sum | format@P(_, '.2f') | rpipe
1 |x| MC.as_integer_ratio() |x| sum |x| format@P(_, '.2f') |x| x

# INTO NEW:
1 |> MC.as_integer_ratio() |> sum |> format@P(_, '.2f')

As you said, it is not exactly call, so I would say __pipe__ / __rpipe__ or __apply__ / __rapply__ would be more accurate. (also, there aren’t any methods with __l prefix - standard is right with __r prefix and left is without)

2. extending current partial would allow for full functionality that you had in mind

_ = functools.Placeholder
__ = functools.ArgsPlaceholder
___ = functools.KwdsPlaceholder
____ = functools.ArgsKwdsPlaceholder

1 |> foo@P(_)
1 |> foo@P(a=_)
[[1], [1]] |> zip@P(__)
{'a': 1, 'b': 2} |> foo@P(___)
([1, 1], {'a': 1, 'b': 2}) |> foo@P(____)


P.S. can we please take the following 2 as a benchmark case for the following ideas so that we can keep perspective on what we are improving?

rpipe(1) | MC.as_integer_ratio() | sum | format@P(_, '.2f') | rpipe
1 |x| MC.as_integer_ratio() |x| sum |x| format@P(_, '.2f') |x| x