{"id":13501316,"url":"https://github.com/JulienPalard/Pipe","last_synced_at":"2025-03-29T08:32:19.135Z","repository":{"id":864424,"uuid":"600956","full_name":"JulienPalard/Pipe","owner":"JulienPalard","description":"A Python library to use infix notation in Python","archived":false,"fork":false,"pushed_at":"2025-03-23T18:09:06.000Z","size":219,"stargazers_count":2012,"open_issues_count":13,"forks_count":116,"subscribers_count":23,"default_branch":"main","last_synced_at":"2025-03-27T04:11:24.857Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JulienPalard.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["JulienPalard"],"liberapay":"JulienPalard"}},"created_at":"2010-04-08T15:39:00.000Z","updated_at":"2025-03-27T00:04:44.000Z","dependencies_parsed_at":"2023-07-07T08:02:01.731Z","dependency_job_id":"20647475-bab5-4c11-a110-4fb0ecdc90b6","html_url":"https://github.com/JulienPalard/Pipe","commit_stats":{"total_commits":117,"total_committers":32,"mean_commits":3.65625,"dds":0.6581196581196581,"last_synced_commit":"fe034cf4304bcb5f3a4e3904c7389097d25f686b"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JulienPalard%2FPipe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JulienPalard%2FPipe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JulienPalard%2FPipe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JulienPalard%2FPipe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JulienPalard","download_url":"https://codeload.github.com/JulienPalard/Pipe/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246162092,"owners_count":20733351,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T22:01:32.955Z","updated_at":"2025-03-29T08:32:19.128Z","avatar_url":"https://github.com/JulienPalard.png","language":"Python","funding_links":["https://github.com/sponsors/JulienPalard","https://liberapay.com/JulienPalard"],"categories":["Python","Topics Index","Awesome Functional Python"],"sub_categories":["QoL Libraries","Libraries"],"readme":"# Pipe — Infix programming toolkit\n\n[![PyPI](https://img.shields.io/pypi/v/pipe)\n ![Monthly downloads](https://img.shields.io/pypi/dm/pipe)\n ![Supported Python Version](https://img.shields.io/pypi/pyversions/pipe.svg)\n](https://pypi.org/project/pipe)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/JulienPalard/pipe/tests.yml?branch=main)](https://github.com/JulienPalard/pipe/actions)\n\nModule enabling a sh like infix syntax (using pipes).\n\n\n# Introduction\n\nAs an example, here is the solution for the [2nd Euler Project\nproblem](https://projecteuler.net/problem=2):\n\n\u003e Find the sum of all the even-valued terms in Fibonacci which do not\n  exceed four million.\n\nGiven `fib` a generator of Fibonacci numbers:\n\n```python\nsum(fib() | where(lambda x: x % 2 == 0) | take_while(lambda x: x \u003c 4000000))\n```\n\nEach pipes is lazy evalatated, can be aliased, and partially\ninitialized, so it could be rewritten as:\n\n```python\nis_even = where(lambda x: x % 2 == 0)\nsum(fib() | is_even | take_while(lambda x: x \u003c 4000000)\n```\n\n\n# Installing\n\nTo install the library, you can just run the following command:\n\n```shell\n# Linux/macOS\npython3 -m pip install pipe\n\n# Windows\npy -3 -m pip install pipe\n```\n\n\n# Using\n\nThe basic syntax is to use a `|` like in a shell:\n\n```python\n\u003e\u003e\u003e from itertools import count\n\u003e\u003e\u003e from pipe import select, take\n\u003e\u003e\u003e sum(count() | select(lambda x: x ** 2) | take(10))\n285\n\u003e\u003e\u003e\n```\n\nSome pipes take an argument:\n\n```python\n\u003e\u003e\u003e from pipe import where\n\u003e\u003e\u003e sum([1, 2, 3, 4] | where(lambda x: x % 2 == 0))\n6\n\u003e\u003e\u003e\n```\n\nSome do not need one:\n\n```python\n\u003e\u003e\u003e from pipe import traverse\n\u003e\u003e\u003e for i in [1, [2, 3], 4] | traverse:\n...     print(i)\n1\n2\n3\n4\n\u003e\u003e\u003e\n```\n\nIn which case it's allowed to use the calling parenthesis:\n\n```python\n\u003e\u003e\u003e from pipe import traverse\n\u003e\u003e\u003e for i in [1, [2, 3], 4] | traverse():\n...     print(i)\n1\n2\n3\n4\n\u003e\u003e\u003e\n```\n\n\n## Existing Pipes in this module\n\nAlphabetical list of available pipes; when several names are listed\nfor a given pipe, these are aliases.\n\n### `batched`\n\nLike Python 3.12 `itertool.batched`:\n\n```python\n\u003e\u003e\u003e from pipe import batched\n\u003e\u003e\u003e list(\"ABCDEFG\" | batched(3))\n[('A', 'B', 'C'), ('D', 'E', 'F'), ('G',)]\n\u003e\u003e\u003e\n```\n\n### `chain`\n\nChain a sequence of iterables:\n\n```python\n\u003e\u003e\u003e from pipe import chain\n\u003e\u003e\u003e list([[1, 2], [3, 4], [5]] | chain)\n[1, 2, 3, 4, 5]\n\u003e\u003e\u003e\n```\n\nWarning : chain only unfolds an iterable containing ONLY iterables:\n\n```python\nlist([1, 2, [3]] | chain)\n```\nGives a `TypeError: 'int' object is not iterable`\nConsider using traverse.\n\n\n### `chain_with(other)`\n\nLike itertools.chain, yields elements of the given iterable,\nthen yields elements of its parameters\n\n```python\n\u003e\u003e\u003e from pipe import chain_with\n\u003e\u003e\u003e list((1, 2, 3) | chain_with([4, 5], [6]))\n[1, 2, 3, 4, 5, 6]\n\u003e\u003e\u003e\n```\n\n### `dedup(key=None)`\n\nDeduplicate values, using the given `key` function if provided.\n\n```python\n\u003e\u003e\u003e from pipe import dedup\n\u003e\u003e\u003e list([-1, 0, 0, 0, 1, 2, 3] | dedup)\n[-1, 0, 1, 2, 3]\n\u003e\u003e\u003e list([-1, 0, 0, 0, 1, 2, 3] | dedup(key=abs))\n[-1, 0, 2, 3]\n\u003e\u003e\u003e\n```\n\n\n### `enumerate(start=0)`\n\nThe builtin `enumerate()` as a Pipe:\n\n```python\n\u003e\u003e\u003e from pipe import enumerate\n\u003e\u003e\u003e list(['apple', 'banana', 'citron'] | enumerate)\n[(0, 'apple'), (1, 'banana'), (2, 'citron')]\n\u003e\u003e\u003e list(['car', 'truck', 'motorcycle', 'bus', 'train'] | enumerate(start=6))\n[(6, 'car'), (7, 'truck'), (8, 'motorcycle'), (9, 'bus'), (10, 'train')]\n\u003e\u003e\u003e\n```\n\n\n### `filter(predicate)`\n\nAlias for `where(predicate)`, see `where(predicate)`.\n\n\n### `groupby(key=None)`\n\nLike `itertools.groupby(sorted(iterable, key = keyfunc), keyfunc)`\n\n```python\n\u003e\u003e\u003e from pipe import groupby, map\n\u003e\u003e\u003e items = range(10)\n\u003e\u003e\u003e ' / '.join(items | groupby(lambda x: \"Odd\" if x % 2 else \"Even\")\n...                  | select(lambda x: \"{}: {}\".format(x[0], ', '.join(x[1] | map(str)))))\n'Even: 0, 2, 4, 6, 8 / Odd: 1, 3, 5, 7, 9'\n\u003e\u003e\u003e\n```\n\n\n### `islice()`\n\nJust the `itertools.islice` function as a Pipe:\n\n```python\n\u003e\u003e\u003e from pipe import islice\n\u003e\u003e\u003e list((1, 2, 3, 4, 5, 6, 7, 8, 9) | islice(2, 8, 2))\n[3, 5, 7]\n\u003e\u003e\u003e\n```\n\n### `izip()`\n\nJust the `itertools.izip` function as a Pipe:\n\n```python\n\u003e\u003e\u003e from pipe import izip\n\u003e\u003e\u003e list(range(0, 10) | izip(range(1, 11)))\n[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10)]\n\u003e\u003e\u003e\n```\n\n### `map()`, `select()`\n\nApply a conversion expression given as parameter\nto each element of the given iterable\n\n```python\n\u003e\u003e\u003e list([1, 2, 3] | map(lambda x: x * x))\n[1, 4, 9]\n\n\u003e\u003e\u003e list([1, 2, 3] | select(lambda x: x * x))\n[1, 4, 9]\n\u003e\u003e\u003e\n```\n\n### `netcat`\n\nThe netcat Pipe sends and receive bytes over TCP:\n\n```python\ndata = [\n    b\"HEAD / HTTP/1.0\\r\\n\",\n    b\"Host: python.org\\r\\n\",\n    b\"\\r\\n\",\n]\nfor packet in data | netcat(\"python.org\", 80):\n    print(packet.decode(\"UTF-8\"))\n```\n\nGives:\n\n```\nHTTP/1.1 301 Moved Permanently\nContent-length: 0\nLocation: https://python.org/\nConnection: close\n```\n\n### ```permutations(r=None)```\n\nReturns all possible permutations:\n\n```python\n\u003e\u003e\u003e from pipe import permutations\n\u003e\u003e\u003e for item in 'ABC' | permutations(2):\n...     print(item)\n('A', 'B')\n('A', 'C')\n('B', 'A')\n('B', 'C')\n('C', 'A')\n('C', 'B')\n\u003e\u003e\u003e\n```\n\n```python\n\u003e\u003e\u003e for item in range(3) | permutations:\n...     print(item)\n(0, 1, 2)\n(0, 2, 1)\n(1, 0, 2)\n(1, 2, 0)\n(2, 0, 1)\n(2, 1, 0)\n\u003e\u003e\u003e\n```\n\n### `reverse`\n\nLike Python's built-in `reversed` function.\n\n```python\n\u003e\u003e\u003e from pipe import reverse\n\u003e\u003e\u003e list([1, 2, 3] | reverse)\n[3, 2, 1]\n\u003e\u003e\u003e\n```\n\n### `select(fct)`\n\nAlias for `map(fct)`, see `map(fct)`.\n\n\n### `skip()`\n\nSkips the given quantity of elements from the given iterable, then yields\n\n```python\n\u003e\u003e\u003e from pipe import skip\n\u003e\u003e\u003e list((1, 2, 3, 4, 5) | skip(2))\n[3, 4, 5]\n\u003e\u003e\u003e\n```\n\n\n### `skip_while(predicate)`\n\nLike itertools.dropwhile, skips elements of the given iterable\nwhile the predicate is true, then yields others:\n\n```python\n\u003e\u003e\u003e from pipe import skip_while\n\u003e\u003e\u003e list([1, 2, 3, 4] | skip_while(lambda x: x \u003c 3))\n[3, 4]\n\u003e\u003e\u003e\n```\n\n### `sort(key=None, reverse=False)`\n\nLike Python's built-in \"sorted\" primitive.\n\n```python\n\u003e\u003e\u003e from pipe import sort\n\u003e\u003e\u003e ''.join(\"python\" | sort)\n'hnopty'\n\u003e\u003e\u003e [5, -4, 3, -2, 1] | sort(key=abs)\n[1, -2, 3, -4, 5]\n\u003e\u003e\u003e\n```\n\n### `t`\n\nLike Haskell's operator \":\":\n\n```python\n\u003e\u003e\u003e from pipe import t\n\u003e\u003e\u003e for i in 0 | t(1) | t(2):\n...     print(i)\n0\n1\n2\n\u003e\u003e\u003e\n```\n\n### `tail(n)`\n\nYields the given quantity of the last elements of the given iterable.\n\n```python\n\u003e\u003e\u003e from pipe import tail\n\u003e\u003e\u003e for i in (1, 2, 3, 4, 5) | tail(3):\n...     print(i)\n3\n4\n5\n\u003e\u003e\u003e\n```\n\n### `take(n)`\n\nYields the given quantity of elements from the given iterable, like `head`\nin shell script.\n\n```python\n\u003e\u003e\u003e from pipe import take\n\u003e\u003e\u003e for i in count() | take(5):\n...     print(i)\n0\n1\n2\n3\n4\n\u003e\u003e\u003e\n```\n\n### `take_while(predicate)`\n\nLike `itertools.takewhile`, yields elements of the\ngiven iterable while the predicate is true:\n\n```python\n\u003e\u003e\u003e from pipe import take_while\n\u003e\u003e\u003e for i in count() | take_while(lambda x: x ** 2 \u003c 100):\n...     print(i)\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n\u003e\u003e\u003e\n```\n\n### `tee`\n\ntee outputs to the standard output and yield unchanged items, useful for\ndebugging a pipe stage by stage:\n\n```python\n\u003e\u003e\u003e from pipe import tee\n\u003e\u003e\u003e sum([\"1\", \"2\", \"3\", \"4\", \"5\"] | tee | map(int) | tee)\n'1'\n1\n'2'\n2\n'3'\n3\n'4'\n4\n'5'\n5\n15\n\u003e\u003e\u003e\n```\n\nThe `15` at the end is the `sum` returning.\n\n\n### `transpose()`\n\nTransposes the rows and columns of a matrix.\n\n```python\n\u003e\u003e\u003e from pipe import transpose\n\u003e\u003e\u003e [[1, 2, 3], [4, 5, 6], [7, 8, 9]] | transpose\n[(1, 4, 7), (2, 5, 8), (3, 6, 9)]\n\u003e\u003e\u003e\n```\n\n### `traverse`\n\nRecursively unfold iterables:\n\n```python\n\u003e\u003e\u003e list([[1, 2], [[[3], [[4]]], [5]]] | traverse)\n[1, 2, 3, 4, 5]\n\u003e\u003e\u003e squares = (i * i for i in range(3))\n\u003e\u003e\u003e list([[0, 1, 2], squares] | traverse)\n[0, 1, 2, 0, 1, 4]\n\u003e\u003e\u003e\n```\n\n### `uniq(key=None)`\n\n\nLike dedup() but only deduplicate consecutive values, using the given\n`key` function if provided (or else the identity).\n\n```python\n\u003e\u003e\u003e from pipe import uniq\n\u003e\u003e\u003e list([1, 1, 2, 2, 3, 3, 1, 2, 3] | uniq)\n[1, 2, 3, 1, 2, 3]\n\u003e\u003e\u003e list([1, -1, 1, 2, -2, 2, 3, 3, 1, 2, 3] | uniq(key=abs))\n[1, 2, 3, 1, 2, 3]\n\u003e\u003e\u003e\n```\n\n### `where(predicate)`, `filter(predicate)`\n\nOnly yields the matching items of the given iterable:\n\n```python\n\u003e\u003e\u003e list([1, 2, 3] | where(lambda x: x % 2 == 0))\n[2]\n\u003e\u003e\u003e\n```\n\nDon't forget they can be aliased:\n\n```python\n\u003e\u003e\u003e positive = where(lambda x: x \u003e 0)\n\u003e\u003e\u003e negative = where(lambda x: x \u003c 0)\n\u003e\u003e\u003e sum([-10, -5, 0, 5, 10] | positive)\n15\n\u003e\u003e\u003e sum([-10, -5, 0, 5, 10] | negative)\n-15\n\u003e\u003e\u003e\n```\n\n## Constructing your own\n\nYou can construct your pipes using the `Pipe` class like:\n\n```python\nfrom pipe import Pipe\nsquare = Pipe(lambda iterable: (x ** 2 for x in iterable))\nmap = Pipe(lambda iterable, fct: builtins.map(fct, iterable)\n\u003e\u003e\u003e\n```\n\nAs you can see it's often very short to write, and with a bit of luck\nthe function you're wrapping already takes an iterable as the first\nargument, making the wrapping straight forward:\n\n```python\n\u003e\u003e\u003e from collections import deque\n\u003e\u003e\u003e from pipe import Pipe\n\u003e\u003e\u003e end = Pipe(deque)\n\u003e\u003e\u003e\n```\n\nand that's it `itrable | end(3)` is `deque(iterable, 3)`:\n\n```python\n\u003e\u003e\u003e list(range(100) | end(3))\n[97, 98, 99]\n\u003e\u003e\u003e\n```\n\nIn case it gets more complicated one can use `Pipe` as a decorator to\na function taking an iterable as the first argument, and any other\noptional arguments after:\n\n```python\n\u003e\u003e\u003e from statistics import mean\n\n\u003e\u003e\u003e @Pipe\n... def running_average(iterable, width):\n...     items = deque(maxlen=width)\n...     for item in iterable:\n...         items.append(item)\n...         yield mean(items)\n\n\u003e\u003e\u003e list(range(20) | running_average(width=2))\n[0, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5]\n\u003e\u003e\u003e list(range(20) | running_average(width=10))\n[0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5]\n\u003e\u003e\u003e\n```\n\n### Organizing pipes more effectively using classes\n\nThe `@Pipe` decorator isn't just for functions-it also works with classes. You can use it with instance methods, class methods, and static methods to better structure your code while keeping it pipeable.\n\n1. Using an Instance Method\n\n```python\n\u003e\u003e\u003e class Factor:\n...     def __init__(self, n: int):\n...         self.n = n\n...     @Pipe\n...     def mul(self, iterable):\n...         return (x * self.n for x in iterable)\n\u003e\u003e\u003e fact = Factor(10)\n\u003e\u003e\u003e list([1, 2, 3] | fact.mul)\n[10, 20, 30]\n\u003e\u003e\u003e\n```\n\n2. Using a Class Method\n\n```python\n\u003e\u003e\u003e class Factor:\n...     n: int = 10\n...     @Pipe\n...     @classmethod\n...     def mul(cls, iterable):\n...         return (x * cls.n for x in iterable)\n\u003e\u003e\u003e list([1, 2, 3] | Factor.mul)\n[10, 20, 30]\n\u003e\u003e\u003e\n```\n\n3. Using a Static Method\n\n```python\n\u003e\u003e\u003e class Factor:\n...     @Pipe\n...     @staticmethod\n...     def mul(iterable):\n...         return (x * 10 for x in iterable)\n\u003e\u003e\u003e list([1, 2, 3] | Factor.mul)\n[10, 20, 30]\n\u003e\u003e\u003e\n```\n\n## One-off pipes\n\nSometimes you just want a one-liner, when creating a pipe you can specify the function's positional and named arguments directly\n\n```python\n\u003e\u003e\u003e from itertools import combinations\n\n\u003e\u003e\u003e list(range(5) | Pipe(combinations, 2))\n[(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]\n\u003e\u003e\u003e\n```\n\na simple running sum with initial starting value\n\n```python\n\u003e\u003e\u003e from itertools import accumulate\n\n\u003e\u003e\u003e list(range(10) | Pipe(accumulate, initial=1))\n[1, 1, 2, 4, 7, 11, 16, 22, 29, 37, 46]\n\u003e\u003e\u003e\n```\n\nor filter your data based on some criteria\n\n```python\n\u003e\u003e\u003e from itertools import compress\n\nlist(range(20) | Pipe(compress, selectors=[1, 0] * 10))\n[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n\u003e\u003e\u003e list(range(20) | Pipe(compress, selectors=[0, 1] * 10))\n[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]\n\u003e\u003e\u003e\n```\n\n## Euler project samples\n\n\u003e Find the sum of all the multiples of 3 or 5 below 1000.\n\n```python\n\u003e\u003e\u003e sum(count() | where(lambda x: x % 3 == 0 or x % 5 == 0) | take_while(lambda x: x \u003c 1000))\n233168\n\u003e\u003e\u003e\n```\n\n\u003e Find the sum of all the even-valued terms in Fibonacci which do not\n\u003e exceed four million.\n\n```python\nsum(fib() | where(lambda x: x % 2 == 0) | take_while(lambda x: x \u003c 4000000))\n```\n\n\u003e Find the difference between the sum of the squares of the first one\n\u003e hundred natural numbers and the square of the sum.\n\n```python\n\u003e\u003e\u003e square = map(lambda x: x ** 2)\n\u003e\u003e\u003e sum(range(101)) ** 2 - sum(range(101) | square)\n25164150\n\u003e\u003e\u003e\n```\n\n\n# Going deeper\n## Partial Pipes\n\nA `pipe` can be parametrized without being evaluated:\n\n```python\n\u003e\u003e\u003e running_average_of_two = running_average(2)\n\u003e\u003e\u003e list(range(20) | running_average_of_two)\n[0, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5]\n\u003e\u003e\u003e\n```\n\nFor multi-argument pipes then can be partially initialized, you can think of curying:\n\n```python\nsome_iterable | some_pipe(1, 2, 3)\nsome_iterable | Pipe(some_func, 1, 2, 3)\n```\n\nis strictly equivalent to:\n\n```python\nsome_iterable | some_pipe(1)(2)(3)\n```\n\nSo it can be used to specialize pipes, first a dummy example:\n\n```python\n\u003e\u003e\u003e @Pipe\n... def addmul(iterable, to_add, to_mul):\n...     \"\"\"Computes (x + to_add) * to_mul to every items of the input.\"\"\"\n...     for i in iterable:\n...         yield (i + to_add) * to_mul\n\n\u003e\u003e\u003e mul = addmul(0)  # This partially initialize addmul with to_add=0\n\u003e\u003e\u003e list(range(10) | mul(10))\n[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]\n\n```\n\nWhich also works with keyword arguments:\n\n```python\n\u003e\u003e\u003e add = addmul(to_mul=1)  # This partially initialize addmul with `to_mul=1`\n\u003e\u003e\u003e list(range(10) | add(10))\n[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n\u003e\u003e\u003e\n```\n\n\nBut now for something interesting:\n\n```python\n\u003e\u003e\u003e import re\n\u003e\u003e\u003e @Pipe\n... def grep(iterable, pattern, flags=0):\n...     for line in iterable:\n...         if re.match(pattern, line, flags=flags):\n...             yield line\n...\n\u003e\u003e\u003e lines = [\"Hello\", \"hello\", \"World\", \"world\"]\n\u003e\u003e\u003e for line in lines | grep(\"H\"):\n...     print(line)\nHello\n\u003e\u003e\u003e\n```\n\nNow let's reuse it in two ways, first with a pattern:\n\n```python\n\u003e\u003e\u003e lowercase_only = grep(\"[a-z]+$\")\n\u003e\u003e\u003e for line in lines | lowercase_only:\n...     print(line)\nhello\nworld\n\u003e\u003e\u003e\n```\n\nOr now with a flag:\n\n```python\n\u003e\u003e\u003e igrep = grep(flags=re.IGNORECASE)\n\u003e\u003e\u003e for line in lines | igrep(\"hello\"):\n...    print(line)\n...\nHello\nhello\n\u003e\u003e\u003e\n```\n\n\n## Lazy evaluation\n\nPipe uses generators all the way down, so it is naturally lazy.\n\nIn the following examples we'll use\n[itertools.count](https://docs.python.org/fr/3/library/itertools.html#itertools.count):\nan infinite generator of integers.\n\nWe'll make use of the `tee` pipe too, which prints every values that\npasse through it.\n\nThe following example does nothing, nothing is printed by `tee` so no\nvalue passed through it. It's nice because generating an infinite\nsequence of squares is \"slow\".\n\n```python\n\u003e\u003e\u003e result = count() | tee | select(lambda x: x ** 2)\n\u003e\u003e\u003e\n```\n\nChaining more pipes still won't make previous ones start generating\nvalues, in the following example not a single value is pulled out of\n`count`:\n\n```python\n\u003e\u003e\u003e result = count() | tee | select(lambda x: x ** 2)\n\u003e\u003e\u003e first_results = result | take(10)\n\u003e\u003e\u003e only_odd_ones = first_results | where(lambda x: x % 2)\n\u003e\u003e\u003e\n```\n\nSame without variables:\n\n```python\n\u003e\u003e\u003e result = (count() | tee\n...                   | select(lambda x: x ** 2)\n...                   | take(10)\n...                   | where(lambda x: x % 2))\n\u003e\u003e\u003e\n```\n\n\nOnly when values are actually needed, the generators starts to work.\n\nIn the following example only two values will be extracted out of `count`:\n- `0` which is squared (to `0`), passes the `take(10)` eaily,\n  but is dropped by `where`\n- `1` which is squared (to `1`), also easily passes the `take(10)`,\n  passes the `where`, and passes the `take(1)`.\n\nAt this point `take(1)` is satisfied so no other computations need to\nbe done. Notice `tee` printing `0` and `1` passing through it:\n\n```python\n\u003e\u003e\u003e result = (count() | tee\n...                   | select(lambda x: x ** 2)\n...                   | take(10)\n...                   | where(lambda x: x % 2))\n\u003e\u003e\u003e print(list(result | take(1)))\n0\n1\n[1]\n\u003e\u003e\u003e\n```\n\n## Deprecations\n\nIn pipe 1.x a lot of functions were returning iterables and a lot\nother functions were returning non-iterables, causing confusion. The\none returning non-iterables could only be used as the last function of\na pipe expression, so they are in fact useless:\n\n```python\nrange(100) | where(lambda x: x % 2 == 0) | add\n```\n\ncan be rewritten with no less readability as:\n\n```python\nsum(range(100) | where(lambda x: x % 2 == 0))\n```\n\nso all pipes returning non-iterables were deprecated (raising\nwarnings), and finally removed in pipe 2.0.\n\n\n## What should I do?\n\nOh, you just upgraded pipe, got an exception, and landed here? You\nhave three solutions:\n\n\n1) Stop using closing-pipes, replace `...|...|...|...|as_list` to\n   `list(...|...|...|)`, that's it, it's even shorter.\n\n2) If \"closing pipes\" are not an issue for you, and you really like\n   them, just reimplement the few you really need, it often take a very\n   few lines of code, or copy them from\n   [here](https://github.com/JulienPalard/Pipe/blob/dd179c8ff0aa28ee0524f3247e5cb1c51347cba6/pipe.py).\n\n3) If you still rely on a lot of them and are in a hurry, just `pip install pipe\u003c2`.\n\n\nAnd start testing your project using the [Python Development\nMode](https://docs.python.org/3/library/devmode.html) so you catch\nthose warnings before they bite you.\n\n\n## But I like them, pleassssse, reintroduce them!\n\nThis has already been discussed in [#74](https://github.com/JulienPalard/Pipe/issues/74).\n\nAn `@Pipe` is often easily implemented in a 1 to 3 lines of code\nfunction, and the `pipe` module does not aim at giving all\npossibilities, it aims at giving the `Pipe` decorator.\n\nSo if you need more pipes, closing pipes, weird pipes, you-name-it,\nfeel free to implement them on your project, and consider the\nalready-implemented ones as examples on how to do it.\n\nSee the `Constructing your own` paragraph below.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJulienPalard%2FPipe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FJulienPalard%2FPipe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJulienPalard%2FPipe/lists"}