Uneval is a small library for working with python-expressions.
It makes working with expressions more intuitive than ast-objects and safer than working on strings.
You may use it to dynamically generate code, write macros or implement domain specific languages,
It also provides a shorthand (F.x) for code that heavily uses anonymous functions.
Make sure to install pip then run:
pip install uneval# Factory to create an expression
expr(obj: str | ast.AST | Expression) -> Expression
# Create variable x as an Expression
var(x: str) -> Expression[ast.Name]
var.x
# Evaluate an expression
evaluate(expr: str | Expression | ast.AST, **vars) -> Any
# Compile an expression
compiled(expr: str | Expression | ast.AST | CodeType) -> CodeType
# Create anonymous function (lambda) with parameter x and expression as body
F.x(expr: str | Expression | ast.AST) -> Callable[[Any], Any]
# Convert expression to abstract syntax tree node
to_ast(expr: Expression | ast.AST) -> ast.ASTimport ast
from uneval import var, evaluate, F
>>> square_x = expr("x * x")
>>> square_x = var.x * var.x # alternative way of writing expressions
>>> evaluate(square_x, x=3)
9
>>> square = F.x(square_x)
>>> square(3)
9
>>> str(square_x)
'x * x'
>>> to_ast(square_x)
ast.BinOp(ast.Name(id="x", ctx=ast.Load()), ast.Mult(), ast.Name(id="x", ctx=ast.Load()))| Factory | Example | Result |
|---|---|---|
expr |
expr("a + 3") |
a + 3 |
var |
var('a') or var.a (shortcut) |
a |
if_ |
if_(var.x >= 0, var.x, -var.x) |
x if x >= 0 else -x |
for_ |
for_(var.x**2, (var.x, range(5))) |
(x**2 for x in range(5)) |
lambda_ |
lambda_([var.x], var.x * var.x) |
lambda x: x * x |
and_, or_ |
and_(var.x >= 10, var.x <= 15) |
x >= 10 and x <= 15 |
not_, in_ |
not_(in_(var.x, {1, 2, 3})) |
not x in {1, 2, 3} |
fstr, fmt |
fstr("sin(", var.a, ") is ", fmt(var.sin(q.a), ".3")) |
f'sin({a}) is {sin(a):.3}' |
Note that expressions don't capture context. In this case scoped can be used instead.
x = 5
# This raises an AttributeError
evaluate(var.x + 3)
# Pass any context variables along that you need.
evaluate(var.x + 3, x=x) # => 8
# Use scoped to capture the surrounding context (including x)
evaluate(scoped(var.x + 3)) # => 8This also applies to modules:
import math
x = 2
evaluate(var.math.sqrt(var.x), x=x, math=math) # => 1.4
evaluate(scoped(var.math.sqrt(var.x))) # => 1.4Libraries that implement something similar:
- Macropy has quasiquote.
- SymPy - Symbolic manipulation, but its representation is different from Python.
- Polars - Writing
col.xcreates something like thisExpression-object.
Other:
- Fixing lambda - A blog post about alternative lambda syntaxes.
- Mini-lambda - Packages to "fix" lambda.
- Meta - A few utils to work on AST's.
- latexify and pytexit - Convert python to LaTeX.
- numexpr and aesara / pytensor - Fast evaluation of numerical expressions.
Useful references:
- python macros use cases - Stack-overflow discussion.
- Green tree snakes - Unofficial documentation of AST nodes.