Skip to content

python-mini-lambda (mini_lambda)

Simple Lambda functions without lambda x: and with string conversion capability

Python versions Build Status Tests Status codecov

Documentation PyPI Downloads Downloads per week GitHub stars

repr is now enabled by default for expressions and functions! More details here

mini_lambda allows developers to write simple expressions with a subset of standard python syntax, without the lambda x: prefix. These expressions can easily be transformed into functions. It is possible to get a string representation of both.

Among many potential use cases, the original motivation came from valid8 where we want to let users provide their own validation functions, while still being able to raise user-friendly exceptions "showing" the formula that failed.

Installing

> pip install mini_lambda

Usage

a- Principles

Three basic steps:

  • import or create a 'magic variable' (an InputVar) such as x, s, l, df...
  • write an expression using it.
  • transform the expression into a function by wrapping it with _(), L(), or F() (3 aliases), or by calling as_function() on it.

For example with a numeric variable:

# -- expressions --
from mini_lambda import x
my_expr = x ** 2
my_expr       # <LambdaExpression: x ** 2>

my_expr(12)   # beware: calling an expression is still an expression !
              # <LambdaExpression: (x ** 2)(12)>

# -- functions --
from mini_lambda import _
my_func = _(x ** 2)  
my_func       # <LambdaFunction: x ** 2>

assert my_func(12) == 144   # calling a function executes it as expected

Or with a string variable:

# create or import a magic variable, here we import 's' 
from mini_lambda import s

# write an expression and wrap it with _() to make a function
from mini_lambda import _
say_hello_function = _('Hello, ' + s + ' !')

# use the function with any input
say_hello_function('world')     # Returns "Hello, world !"

# the function's string representation is available
print(say_hello_function)       # "'Hello, ' + s + ' !'"

b- Capabilities

The variable can represent anything, not necessarily a primitive. If you wish to use another symbol just define it using InputVar:

from mini_lambda import InputVar
z = InputVar('z')

from logging import Logger
l = InputVar('l', Logger)

Note that the type information is optional, it is just for your IDE's autocompletion capabilities.

Most of python syntax can be used in an expression:

from mini_lambda import x, s, _
from mini_lambda.symbols.math_ import Log

# various lambda functions
is_lowercase             = _( s.islower() )
get_prefix_upper_shebang = _( s[0:4].upper() + ' !' )
numeric_test_1           = _( -x > x ** 2 )
numeric_test_2           = _( ((1 - 2 * x) <= -x) | (-x > x ** 2) )
complex_identity         = _( Log(10 ** x, 10) )

# use the functions
is_lowercase('Hello')              # returns False
get_prefix_upper_shebang('hello')  # returns 'HELL !'
numeric_test_1(0.5)                # returns False
numeric_test_2(1)                  # returns True
complex_identity(10)               # returns 10

# string representation
print(is_lowercase)             # s.islower()
print(get_prefix_upper_shebang) # s[0:4].upper() + ' !'
print(numeric_test_1)           # -x > x ** 2
print(numeric_test_2)           # (1 - 2 * x <= -x) | (-x > x ** 2)
print(complex_identity)         # log(10 ** x, 10)

If you know python you should feel at home here, except for two things:

  • or and and should be replaced with their bitwise equivalents | and &
  • additional constants, methods and classes need to be made lambda-friendly before use. For convenience all of the built-in functions as well as constants, methods and classes from the math.py and decimal.py modules are provided in a lambda-friendly way by this package, hence the from mini_lambda.symbols.math_ import Log above.

Note that the printed version provides the minimal equivalent representation taking into account operator precedence. Hence numeric_test_2 got rid of the useless parenthesis. This is not a mathematical simplification like in SymPy, i.e. x - x will not be simplified to 0.

There are of course a few limitations to mini_lambda as compared to full-flavoured python lambda functions, the main ones being that

  • you can't mix more than one variable in the same expression for now. The resulting functions therefore have a single argument only.
  • list/tuple/set/dict comprehensions are not supported
  • ... if ... else ... ternary conditional expressions are not supported either

Check the Usage page for more details.

New: repr now enabled by default

Starting in version 2.0.0, the representation of lambda expressions does not raise exceptions anymore by default. This behaviour was a pain for developers, and was only like this for the very rare occasions where repr was needed in the expression itself.

So now

>>> from mini_lambda import x, F
>>> x ** 2
<LambdaExpression: x ** 2>
>>> F(x ** 2)
<LambdaFunction: x ** 2>

If you wish to bring back the old exception-raising behaviour, simply set the repr_on attribute of your expressions to False:

>>> from mini_lambda import x
>>> x.repr_on = False
>>> x ** 2
(...)
mini_lambda.base.FunctionDefinitionError: __repr__ is not supported by this Lambda Expression. (...)

c- How to support mini-lambda expressions in your libraries.

You may wish to support mini-lambda expressions (not functions) directly into your code. That way, your users will not even have to convert their expressions into functions - this will bring more readability and ease of use for them.

You can do this with as_function: this will convert expressions to functions if needed, but otherwise silently return its input.

from mini_lambda import _, s, as_function

def call_with_hello(f):
    """An example custom method that is lambda_friendy"""

    # transform mini-lambda expression to function if needed.
    f = as_function(f)

    return f('hello')

# it works with a normal function
def foo(s):
    return s[0]
assert call_with_hello(foo) == 'h'

# with a mini-lambda *Function* (normal: this is a function)
assert call_with_hello(_(s[0])) == 'h'

# and with a mini-lambda *Expression* too (this is new and easier to read)
assert call_with_hello(s[0]) == 'h'

In addition a is_mini_lambda_expr helper is also provided, if you wish to perform some reasoning:

from mini_lambda import x, is_mini_lambda_expr, as_function

# mini lambda: true
assert is_mini_lambda_expr(x ** 2)

# standard lambda: false
assert not is_mini_lambda_expr(lambda x: x)

# standard function: false
def foo():
    pass
assert not is_mini_lambda_expr(foo)

# mini lambda as function: false
f = as_function(x ** 2)
assert not is_mini_lambda_expr(f)

Main features

  • More compact lambda expressions for single-variable functions
  • As close to python syntax as technically possible: the base type for lambda expressions in mini_lambda, LambdaExpression, overrides all operators that can be overriden as of today in python. The remaining limits come from the language itself, for example chained comparisons and and/or are not supported as python casts the partial results to boolean to enable short-circuits. Details here.
  • Printability: expressions can be turned to string representation in order to (hopefully) get interpretable messages more easily, for example when the expression is used in a validation context

See Also

The much-broader debate in the python community about alternate lambda syntaxes is interesting, see here

Equivalent (python-first)

I found the following libraries somehow covering the same use case, with more or less success/features:

  • SymPy is the most well known symbolic computation framework in python. It provides a printable Lambda() object, but it does not seem to support all operators (see this post).
  • lambdaX
  • lambdazen. Based on python source code generation at runtime using a decorator. The main drawback is the need to define lambdas inside a decorated function.
  • fixing lambda and its associated toy library quicklambda. It is not very exhaustive.
  • pyexpression is quite similar to quicklambda (above)
  • fz is also inspired by quicklambda (above). Note: it is GPL-licensed.

A bit far from the topic but related: * letexpr for let expression like Haskell * calchylus: lisp-like expressions in python based on Hy * MiniOperators

String expression-first

These libraries create functions from string expressions. Therefore you cannot rely on your favourite IDE to check your expressions, but it might not be a problem for some users/use cases.

Others

Do you like this library ? You might also like my other python libraries

Want to contribute ?

Details on the github page: https://github.com/smarie/python-mini-lambda