Skip to content

A library adding some anti-Pythonic syntatic sugar to Python

Notifications You must be signed in to change notification settings

czheo/syntax_sugar_python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

syntax_sugar travis_status PyPI

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.

Stream Model

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: 'https://' + 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
# [('https://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