Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

funcoperators-0.8.2.tar.gz (9.2 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page