Pipe: Infix syntax for Python
Posted on Tue 04 April 2017 in blog
Pipe is a Python module enabling infix syntax in Python. For those asking "Why ?" let's take an example: Compare the readability of the classical prefix syntax:
sum(select(where(take_while(fib(), lambda x: x < 1000000) lambda x: x % 2), lambda x: x * x))
And the infix syntax:
fib() | take_while(lambda x: x < 1000000) \
| where(lambda x: x % 2) \
| select(lambda x: x * x) \
| sum()
Isn't the infix syntax more readable? The base class of Pipe is kept
simple (7 lines of python) and is usable as a decorator permitting you
to create new 'pipeable' functions easily. The module provides like 30
prepared pipes functions like where
, group_by
, sort
,
take_while
... A pipeable function takes an iterable (tuple
, list
,
generator
) and yields to be itself an iterator, so pipeable function can
be piped together. Let me introduce the basic usage of the Pipe
module,
then I'll write some bits on how to build new ones: To start, get it
from PyPI http://pypi.python.org/pypi/pipe/1.3 and install it, open a
REPL, import pipe, and play:
Python 2.6.6 (r266:84292, Dec 26 2010, 22:31:48)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pipe import *
>>> [1, 2, 3, 4, 5] | add
15
>>> [5, 4, 3, 2, 1] | sort
[1, 2, 3, 4, 5]
Until here it's easy, to know more about available pipes, just read the
help(pipe)
in the REPL, all are explained with an example as a doctest
Now as we know that pipeable functions use iterables, we can try to pipe
together two or more pipeables:
>>> [1, 2, 3, 4, 5] | where(lambda x: x % 2) | concat
'1, 3, 5'
>>> [1, 2, 3, 4, 5] | where(lambda x: x % 2) | tail(2) | concat
'3, 5'
>>> [1, 2, 3, 4, 5] | where(lambda x: x % 2) | tail(2) | select(lambda x: x * x) | concat
'9, 25'
>>> [1, 2, 3, 4, 5] | where(lambda x: x % 2) | tail(2) | select(lambda x: x * x) | add
34
Now, a bit about lazyness, as Pipe use iterables, the evaluation of a whole Pipe is lazy, so we can play with infinite generators like this one :
>>> def fib():
... a, b = 0, 1
... while True:
... yield a
... a, b = b, a + b
Now we can do every kind of stuff into the fibonacci sequence, like solving the 2nd problem of http://projecteuler.net in a readable one liner:
Find the sum of all the even-valued terms in Fibonacci which do not exceed four million.
>>> euler2 = fib() | where(lambda x: x % 2 == 0) | take_while(lambda x: x < 4000000) | add
>>> assert euler2 == 4613732
Isn't it pretty? Let now see how to create new pipeable functions using
the @pipe
decorator: You want to create a function that yields the
first x elements from its input You want its usage to be (1, 2, 3, 4, 5)
| take(2)
to take the fist 2 elements. I know that you are thinking
about a basic implementation like:
def take(iterable, qte):
for item in iterable:
if qte > 0:
qte -= 1
yield item
else:
return
Right? You take an iterable, a qantity, and while the quantity is not
reached, you just have to yield? OK, just add @pipe
to you take
function and it's pipeable :-)