94 lines
3.3 KiB
Markdown
94 lines
3.3 KiB
Markdown
Title: Pipe: Infix syntax for Python
|
|
|
|
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:
|
|
|
|
:::python
|
|
sum(select(where(take_while(fib(), lambda x: x < 1000000) lambda x: x % 2), lambda x: x * x))
|
|
|
|
And the infix syntax:
|
|
|
|
:::python
|
|
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:
|
|
|
|
:::pycon
|
|
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:
|
|
|
|
:::pycon
|
|
>>> [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 :
|
|
|
|
:::pycon
|
|
>>> 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.
|
|
|
|
:::pycon
|
|
>>> 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:
|
|
|
|
:::python
|
|
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 :-)
|