mdk.fr/content/blog/2022-pipe-v2.md

141 lines
3.3 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
Title: Releasing pipe v2.0
Summary: Introducing partially parametrized pipes!
---
# Introducing partially parametrized pipes!
[pipe](https://github.com/JulienPalard/Pipe) is a very old project of mine, proudly showing 1.3k stars on github!
I don't maintain it a lot, but today I'm announcing a new release introducing a nice feature: partially parametrized pipes.
If you're familiar with [currying](https://en.wikipedia.org/wiki/Currying)
or
[functools.partial](https://docs.python.org/3/library/functools.html#functools.partial)
you won't get lost.
## Show me!
Before `pipe 2`, the following were already valid:
```python
>>> from random import randint
>>> from pipe import where, sort
>>>
>>> negative = where(lambda x: x < 0)
>>> positive = where(lambda x: x > 0)
>>>
>>> numbers = [randint(-10, 10) for _ in range(10)]
>>>
>>> numbers | positive | sort
[7, 10]
>>> numbers | negative | sort
[-10, -9, -8, -7, -4, -4, -2]
```
It allows to **name things**, naming things make
code more readable so I wanted more of it.
It was probably underrated, I like to do this kind of things:
```python
>>> isort = sort(key=lambda x: x**2)
>>> numbers | isort
[0, -2, -4, -4, -7, 7, -8, -9, 10, -10]
```
But it failed as soon as you try go deeper:
```python
>>> numbers | isort(reverse=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pipe.py", line 100, in __ror__
return self.function(other)
File "pipe.py", line 103, in <lambda>
return Pipe(lambda x: self.function(x, *args, **kwargs))
TypeError: Pipe.__call__.<locals>.<lambda>() got an unexpected keyword argument 'reverse'
```
Call it a bug, that's what's pipe 2 fixes:
```python
>>> from pipe import where, sort
>>> from random import randint
>>> numbers = [randint(-10, 10) for _ in range(10)]
>>>
>>> isort = sort(key=lambda x: x**2)
>>> numbers | isort(reverse=True)
[-10, -9, 8, -6, 6, -4, 4, 4, 2, 1]
>>>
```
Or in the other way around:
```python
>>> rsort = sort(reverse=True)
>>> numbers | rsort
[8, 6, 4, 4, 2, 1, -4, -6, -9, -10]
>>> numbers | rsort(key=lambda x: x**2)
[-10, -9, 8, -6, 6, -4, 4, 4, 2, 1]
```
From here you can build many specialized blocks from a single pipe,
here's a better example:
```python
import re
from pipe import Pipe
@Pipe
def grep(iterable, pattern, flags=0, invert=False):
"""Mimics grep."""
for line in iterable:
match = re.match(pattern, line, flags=flags)
if (match and not invert) or (not match and invert):
yield line
```
Build the small reusable blocks:
```python
igrep = grep(flags=re.I)
vgrep = grep(invert=True)
vigrep = igrep(invert=True)
https = igrep("https://")
not_https = https(invert=True)
...
```
And they all do what you think they do:
```python
>>> lines = ["https://python.org", "http://detectportal.firefox.com/", "Just no an URL", "HTTPS://afpy.org"]
>>> for url in lines | https:
... print(url)
...
https://python.org
HTTPS://afpy.org
```
```python
>>> for url in lines | not_https:
... print(url)
...
http://detectportal.firefox.com/
Just no an URL
```
Remember I said currying? Yes you can abuse the syntax, but please don't:
```python
>>> from pipe import sort
>>> lines | grep()(invert=True)()(flags=re.I)()("https://")() | sort
['Just not an URL', 'http://detectportal.firefox.com/']
```
As I like to say: « It's not because it's possible that you should do it ».