Allow natural function notations like (1,2) /dot/ (3,4) for dot((1,2), (3,4)) or 1 /frac/ 3 for Fraction(1,3), pipes and other useful operators to functions.
Project description
Always wanted to add custom operators ?
a = (1,2,3) /dot/ (4,5,6) # a = 32
infix
Infix operators can be created using the infix
class.
It works for existing functions, like numpy.dot
:
import numpy
dot = infix(numpy.dot)
a = (1,2,3) /dot/ (4,5,6) # use as an infix
If you already have dot
in your namespace, don't worry, it still work as a function:
a = dot((1,2,3), (4,5,6)) # still works as a function
Or for custom functions as a decorator:
@infix
def crunch(x,y):
"""
Do a super crunchy operation between two numbers.
"""
return x + 2 * y
a = 1 |crunch| 2 # a = crunch(1, 2)
a = crunch(1, 2) # still works
help(crunch.function) # to get help about the initial function
Any binary operator can be used like 1 *f* 2
, 1 %f% 2
or 1 << f << 2
but /
or |
should be clean for all use cases.
Beware if you use **
, the operator is right to left:
b = 1 **f** 2 # a = f(2, 1)
dot and cross product
Dot and cross products are used heavily in mathematics and physics as an infix operator ·
or ×
.
a = (1,2,3) /dot/ (4,5,6)
a = (1,2,3) |dot| (4,5,6) # same
r = 2 + (1,2,3) /dot/ (4,5,6) # here "/" has priority over + like in normal python
r = 2 + (1,2,3) *dot* (4,5,6) # for a dot PRODUCT, '*' seems logical
r = 2 + dot((1,2,3), (4,5,6)) # still works as a function
using '|' for low priority
In some use cases, one want to mix classic operators with function operators,
the |
operator may be used as a low priority operator.
Y = A + B |dot| C # is parsed as Y = (A + B) |dot| C
Y = A + B /dot/ C # is parsed as Y = A + (B /dot/ C)
fractions
When using the fractions
module, often you want to transition from floats
to Fraction
.
Your current code uses /
for division and you can just replace the slashes with /frac/
, the mathematical stays natural to read!
from fractions import Fraction
frac = infix(Fraction)
a = 1 + 1 / 3 # 1.3333...
a = 1 + 1 /frac/ 3 # Fraction(4, 3)
b = 2 * (a + 3) /frac/ (a + 1) # nicer complex expressions
ranges, 2..5 in ruby ?
In many languages, iterating over a range has a notational shortcut, like 2..5
in ruby.
Now you can even write for i in 1 /inclusive/ 5
in python.
@infix
def inclusive(a,b):
return range(a, b+1)
for i in 2 /inclusive/ 5:
print(i) # 2 3 4 5
for i in inclusive(2, 5):
print(i) # 2 3 4 5
pipes : postfix
In bash, a functionality called pipes is useful to reuse an expression and change the behavior by just adding code at the end. The library can be used for that.
@postfix
def no_zero(L):
return [x for x in L if x != 0]
@postfix
def plus_one(L):
return [x+1 for x in L]
Y = [1,2,7,0,2,0] |no_zero |plus_one # Y == [2,3,8,3]
Y = plus_one(no_zero([1,2,7,0,2,0])) # Y == [2,3,8,3]
pipe factory
Sometimes, pipes want extra information, for example in our last example, no_zero
is a special case of a pipe that filters out a value,
use the pipe factory
recipe like so:
def filter_out(x):
@postfix
def f(L):
return [y for y in L if y != x]
return f
L = [1,2,7,0,2,0] | filter_out(0) # Y == [2,3,8,3]
function compose (alias circle)
In mathematics and functional programming languages, function composition is naturally used using a circle
operator to write things like h = f ∘ g
.
s = hex(ord('A')) # s = '0x41'
from funcoperators import compose
display = hex /compose/ ord
s = display('A') # s = '0x41'
display = hex *circle* ord
partial syntax
The library adds sugar to functools, called curry
, the names comes from other languages.
def f(x,y):
return x + y
from funcoperators import curry
g = f /curry/ 5
y = f(2) # y = 7
from funcoperators import partially
@partially
def f(x,y,z):
return x + y + z
r = f(1,2,3)
r = f[1](2,3)
r = f[1][2][3]()
# NOT: f[1,2] which will give one argument: a tuple
partiallyauto
In functional languages, function composition is sometimes not dissociable from function call,
partiallyauto
only works for methods with N fixed positional arguments.
@partiallyauto
def f(x,y,z):
return x + y + z
r = f(1,2,3) # r = 6
r = f(1)(2)(3) # r = 6
r = f(1)(2,3) # r = 6
g = f(1) # g = a function with two arguments
r = g(2,3) # r = 6
k = g(2) # k = a function with one argument
elipartial, elicurry (alias with_arguments and deferredcall)
Python's functools.partial
only works for arguments that will be provided later, one must use keywords arguments.
However, not all functions do keywords arguments.
square = curryright(pow, 2) # square(x) = x ** 2
The library also proposes to use Python's ...
(Ellipsis
) as a natural placeholder for arguments.
tenexp = elipartial(pow, 10) # = pow(10, something)
y = tenexp(2) # 10 ** 2
square = elipartial(pow, ..., 2) # = pow(something, 2)
y = square(5) # 5 ** 2
Here as a more complex example, we define show
to be the print
function with arguments 1
, something, 3
and keyword argument sep='.'
.
show = print | elicurry(1, ..., 3, sep='/')
show(2) # prints 1/2/3
see more examples in the test cases in source code
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.