syntax_sugar
This lib adds some anti-Pythonic "syntactic sugar" to Python.
NOTE: This is merely an experimental prototype to show some potential of operator overloading in Python. Only tested under Python 3.6.0. Anything may evolve without announcement in advance.
Inspired by https://github.com/matz/streem.
Also, you can watch the last part of this Matz's talk to understand the intuition behind this project.
Install
pip install syntax_sugar
Use
To test out this lib, you can simply do.
from syntax_sugar import *
For serious use, you can explicitly import each component as explained below ... if you dare to use this lib.
pipe
from syntax_sugar import pipe, END
from functools import partial
pipe(10) | range | partial(map, lambda x: x**2) | list | print | END
# put 10 into the pipe and just let data flow.
# output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# remember to call END at the end
# NOTE: everything in the middle of the pipe is just normal Python functions
pipe(10) | range | (map, lambda x: x**2) | list | print | END
# Tuples are shortcuts for partial functions
from syntax_sugar import each
x = pipe(10) | range | each(lambda x: x ** 2) | END
# We can also save the result in a variable.
# `each` is an eager evaluated version of the partial function of `map`, which returns a list instead of a map object. (Equivalent to `map` in Python 2)
pipe(10) | range | each(str) | ''.join > 'test.txt'
# wanna write to a file? Why not!
# write "0123456789" to test.txt
# We don't need to put END here.
We can connect multiple pipes to create a longer pipe
from syntax_sugar import pipe, each, END
from functools import reduce
p1 = pipe(10) | range | each(lambda x: x/2)
# head pipe can have input value
p2 = pipe() | (reduce, lambda acc, x: (acc + x)/2)
p3 = pipe() | int | range | sum
# middle pipes can have no input value
p1 | p2 | p3 | END
# returns 6
p = p1 | p2 | p3
p()
# You can invoke the pipe by calling it as a function
# you can also put a different value in the pipe
p(20)
# returns 36
pipe with parallelism
By default, pipe works with threads.
You can have a function running in a seperate thread with pipe. Just put it in a []
or more explicitly t[]
. Threads and processes are also available.
from syntax_sugar import (thread_syntax as t,
process_syntax as p)
pipe(10) | [print] | END # print run in a thread
pipe(10) | t[print] | END # print run in a thread
pipe(10) | p[print] | END # print run in a process
What makes this syntax good is that you can specify how many threads you want to spawn, by doing [function] * n
where n
is the number of threads.
pipe([1,2,3,4,5]) | [print] * 3 | END # print will run in a ThreadPool of size 3
Here is an example of requesting a list of urls in parallel
import requests
(pipe(['google', 'twitter', 'yahoo', 'facebook', 'github'])
| each(lambda name: 'http://' + name + '.com')
| [requests.get] * 3 # !! `requests.get` runs in a ThreadPool of size 3
| each(lambda resp: (resp.url, resp.headers.get('Server')))
| list
| END)
# returns
# [('http://www.google.com/', 'gws'),
# ('https://twitter.com/', 'tsa_a'),
# ('https://www.yahoo.com/', 'ATS'),
# ('https://www.facebook.com/', None),
# ('https://github.com/', 'GitHub.com')]
infix function
from syntax_sugar import is_a, has, to, step, drop
1 /is_a/ int
# equivalent to `isinstance(1, int)`
1 /as_a/ str
# "1"
range(10) /has/ '__iter__'
# equivalent to `hasattr(range(10), "__iter__")`
1 /to/ 10
# An iterator similar to `range(1, 11)`.
# Python's nasty range() is right-exclusive. This is right-inclusive.
10 /to/ 1
# We can go backward.
'0' /to/ '9'
# We can also have a range of characters :)
1 /to/ 10 /step/ 2
# We can also specify step sizes.
# Similar to `range(1, 11, 2)`
10 /to/ 1 /step/ 2
# Go backward.
# Similar to `range(10, 0, -2)`
1 /to/ 10 /drop/ 5
# there is a `drop` functon which drop N items from the head
# An iterator similar to [6, 7, 8, 9, 10]
/to/
has some advanced features
- lazy evaluation.
- support infinity.
- support product operation.
- support pipe.
from syntax_sugar import INF, take, each
# CAUTION: this will infinitely print numbers
for i in 1 /to/ INF:
print(i)
1 /to/ INF /take/ 5 /as_a/ list
# there is a `take` functon which is similar to itertools.islice
# return [1, 2, 3, 4, 5]
1 /to/ ... /take/ 5 /as_a/ list
# ... is equivalent to INF
0 /to/ -INF /step/ 2 /take/ 5 /as_a/ list
# also works with negative infinity.
# return [0, -2, -4, -6, -8]
(1 /to/ 3) * (4 /to/ 6) /as_a/ list
# all combinations of [1..3] * [4..6]
# return [(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]
1 /to/ 10 /take/ 5 | each(lambda x: x **2) | END
# These infix functions can also be piped.
# [1, 4, 9, 16, 25]
Make your own infix function, so you can append multiple items to a list in one line.
from syntax_sugar import infix
@infix
def push(lst, x):
lst.append(x)
return lst
[] /push/ 1 /push/ 2 /push/ 3
# returns [1,2,3]
You can also do
def push(lst, x):
lst.append(x)
return lst
ipush = push /as_a/ infix
[] /ipush/ 1 /ipush/ 2 /ipush/ 3
# returns [1,2,3]
function composition
In math, (f * g) (x) = f(g(x))
. This is called function composition.
# lmap equivalent to `list(map(...))`
lmap = compose(list, map)
lmap(lambda x: x ** 2, range(10))
Let's say we want to represent f * g * h
in a program, i.e. fn(x) = f(g(h(x)))
f = lambda x: x**2 + 1
g = lambda x: 2*x - 1
h = lambda x: -2 * x**3 + 3
fn = compose(f, g, h)
fn(5) # 245026
or you can do
f = composable(lambda x: x**2 + 1)
g = composable(lambda x: 2*x - 1)
h = composable(lambda x: -2 * x**3 + 3)
fn = f * g * h
fn(5) # 245026
Sometimes you may prefer the decorator way.
# make your own composable functions
@composable
def add2(x):
return x + 2
@composable
def mul3(x):
return x * 3
@composable
def pow2(x):
return x ** 2
fn = add2 * mul3 * pow2
# equivalent to `add2(mul3(pow2(n)))`
fn(5)
# returns 5^2 * 3 + 2 = 77
More receipes: https://github.com/czheo/syntax_sugar_python/tree/master/recipes