Skip to content

lverweijen/python-uneval

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

uneval

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.

Installation

Make sure to install pip then run:

pip install uneval

Usage

Functionality

# 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.AST

Example

import 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()))

Building blocks

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}'

Keeping track of context

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))  # => 8

This 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.4

Similar libraries

Libraries that implement something similar:

  • Macropy has quasiquote.
  • SymPy - Symbolic manipulation, but its representation is different from Python.
  • Polars - Writing col.x creates something like this Expression-object.

Other:

Useful references:

About

Library for generating python-expressions

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages