Skip to main content

Superpipe: Elixir-style Pipes for Python

In the Elixir programming language the |> pipe operator allows you to chain together multiple function calls to avoid nesting. It allows for this:

c(b(a(1, 2), 3, 4))

to be writen as this:

1 |> a(2) |> b(3, 4) |> c()

Superpipe implements a more powerful version of this operator in Python, the superpipe operator (henceforth, just "the pipe operator").

Superpipe allows you to turn heavily-nested code into easily-readable flows of data from left to right.

For example:

# Take all the numbers [0, 100) that have an odd-number bit length, square them, and print the result
print("The numbers:", map(lambda x: x * x, filter(lambda x: x.bit_length() % 2 != 0, range(100))))

Using superpipe:

range(100) >> filter(lambda x: x.bit_length() % 2 != 0) >> map(lambda x: x * x) >> print("The numbers:", _)

Install

Get it on PyPi: https://pypi.org/project/superpipe/

Tutorial

# To begin using the pipe operator, you need to import the decorator

from superpipe import pipes

# And then apply it to a function, method, or class

@pipes
def foo():
    5 >> print

@pipes
class Bar:
    def __init__(self):
        self.qux = 5 >> _ * 2

class Loq:
    @pipes
    def vaz():
        "superpipes!" >> print
# The @pipes decorator transforms the right-shift operator >> into the pipe operator
# You insert it between two expressions to inject the value of the lefthand side into the right

# The simplest example is to pass the lefthand side as a function argument

5 >> print
# The same as print(5)

5 >> print()
# Also the same as print(5)

# The pipe operator always adds the argument to the end of argument list

5 >> print("abc,")
# This prints "abc, 5"

range(3) >> map(lambda x: x)
# The operator really shines with functional constructs
# This is the same as map(lambda x: x, range(3))
# > But I don't want it to be the last argument!
# You can use the special implicit substitution identifier "_" (an underscore)
# Superpipe will substitute the lefthand side into where-ever it finds it

5 >> print(_, "abc")
# This prints "5 abc"

5 >> print(_)
# This is the same as earlier

5 >> print(_, _)
# Prints "5 5"
# > But I want to call a method on the lefthand side!
# You can do this via the _ identifier. In fact, you can use the underscore almost anywhere

"superpipe" >> _.title()
# This is the same as "superpipe".title()

5 >> _ + 1
# The same as 5 + 1

"xyz" >> f"look at this!: {_}. wow."
# f-strings

[1, 2, 3] >> _[0]
# This gets the first element out of the list

def foo(a, b, c):
    pass

{"c": 1, "a": 2, "b": 3} >> foo(**_)
# You can use star-expansion on _!

for x in range(3) >> map(lambda x: x * x):
    pass
# You can use superpipes in loops!

print(
    5
    >> _ + 1
    >> _ * _
)
# In some contexts Python syntax allows for spreading over multiple lines, like in a function call
# In other contexts, you can use a backslash (\) to split the line over multiple
# This prints 36

# You may be surprised at the number of things superpipe can do
# Try things out, and check out `tests/test.py` for a demonstration of everything it can do

How it Works and Performance Considerations

When the pipe decorator is applied to a function it grabs the source code using the inspect module, parses it using the ast module, performs recursive transformations on the tree, and then substitutes the original function with the result.

Generally speaking, code written using superpipe will perform the same as writing the nested code explicitly, with two major caveats:

  1. The first time Python evaluates your function and the decorator runs, there is a small overhead due to the AST transformations. This overhead should be relatively low and a one-time cost, happening only the first time the function is seen.
  2. The pipe operator does not optimize for multiple _ substitutions in the same expression. When the decorator encounters the substitution identifier it substitutes all of the code to the left into the expression. When it has to do multiple such substitutions the lefthand side will be evaluated multiple times. For example, 5*5 >> print(_) becomes print(5*5), however 5*5 >> print(5*5, 5*5), thus performing the calculation twice, whereas one could write it more efficiently as twentyfive = 5*5; print(twentyfive, twentyfive). This example is trivial, but gets worse the more nesting there is. This can be avoided by carefully considering where you perform multiple substitutions, and breaking up long chains into multiple.

Feedback, Comments, Improvements?

Please open an issue on the repository, I would be happy to discuss with you.


Thank you to Robin Hilliard for the original inspiration for this project, the one from which this one was forked. Please consider visiting his implementation here: robinhilliard/pipes.

Metadata

Release files for superpipe 1.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for superpipe 1.0.3
File Size Uploaded Provenance Actions
superpipe-1.0.3.tar.gz 8.3 kB Without provenance Details for superpipe-1.0.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for superpipe 1.0.3
File Interpreter ABI Platform Actions
superpipe-1.0.3-py3-none-any.whl Python 3 none any Details for superpipe-1.0.3-py3-none-any.whl

Total release size: 16.4 kB

Release files / superpipe-1.0.3.tar.gz

Download URL superpipe-1.0.3.tar.gz
Size 8.3 kB
Tags Source
SHA-256 checksum
How to use checksums
1e6c100c7f21d56bc0526ff4078587cafe372b2fdc096ea5faf36276104408a0
BLAKE2b-256 checksum
How to use checksums
959d34d4a6a48951ccb8f8504580e46761036389d49c58cd41b55f87d76b7b44
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.4.1 importlib_metadata/4.6.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.61.2 CPython/3.9.5

Release files / superpipe-1.0.3-py3-none-any.whl

Download URL superpipe-1.0.3-py3-none-any.whl
Size 8.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5a15855fd5510202a0ecf301bebc714fe2243a852d084538837412c8be4adbb8
BLAKE2b-256 checksum
How to use checksums
483913be805b7592c7ec9c8efef9e6d568f4aa2956c67ed762e0ba04fabe8db4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.4.1 importlib_metadata/4.6.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.61.2 CPython/3.9.5

Security for superpipe 1.0.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page