expression.py
Mathematical expression evaluation library for Python, built with a PEG parser generated by
pegen. An alternative to
safeeval that supports more Ruby operators like =~ and array
operators.
This is a Python port of jcubic/expression.php.
Installation
pip install expression-py
Usage
from expression import Expression
expr = Expression()
# Basic arithmetic
expr.evaluate("2 + 3 * 4") # 14
expr.evaluate("(2 + 3) * 4") # 20
expr.evaluate("2 ** 10") # 1024
expr.evaluate("10 % 3") # 1
# Floating point
expr.evaluate("0.1 + 0.2") # 0.30000000000000004
expr.evaluate("1.5e2") # 150.0
# Hex and binary literals
expr.evaluate("0xFF") # 255
expr.evaluate("0b1010") # 10
# Bitshift operators
expr.evaluate("100 >> 2") # 25
expr.evaluate("100 << 2") # 400
Variables
expr = Expression()
# Assignment
expr.evaluate("x = 10")
expr.evaluate("x + 2") # 12
# Pre-set variables
expr.variables = {"width": 100, "height": 50}
expr.evaluate("width * height") # 5000
Built-in Constants and Functions
expr = Expression()
# Constants
expr.evaluate("pi") # 3.141592653589793
expr.evaluate("e") # 2.718281828459045
# Math functions
expr.evaluate("sqrt(16)") # 4.0
expr.evaluate("sin(pi / 2)") # 1.0
expr.evaluate("abs(-42)") # 42
expr.evaluate("log(e)") # 1.0
Available built-in functions: sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh, sqrt, abs, log, ln.
Aliases: arcsin/arccos/arctan/arcsinh/arccosh/arctanh map to their a-prefixed equivalents. ln is an alias for log.
Custom Functions
expr = Expression()
# Define functions in the expression language
expr.evaluate("f(x, y) = x + y")
expr.evaluate("f(10, 20)") # 30
expr.evaluate("square(x) = x * x")
expr.evaluate("square(5)") # 25
# Register Python functions
expr.functions["even"] = lambda x: x % 2 == 0
expr.evaluate("even(4)") # True
# Access defined functions from Python
expr.evaluate("add(a, b) = a + b")
expr.functions["add"](3, 4) # 7
Implicit Multiplication
expr = Expression()
expr.evaluate("f(x) = 2x")
expr.evaluate("f(5)") # 10
expr.evaluate("x = 3")
expr.evaluate("2x") # 6
expr.evaluate("2(3 + 4)") # 14
Boolean and Comparison Operators
expr = Expression()
# Comparisons
expr.evaluate("10 == 10") # True
expr.evaluate("10 != 20") # True
expr.evaluate("10 > 5") # True
expr.evaluate("10 <= 10") # True
# Strict equality (type-aware)
expr.evaluate("2 === 2") # True
expr.evaluate("'2' !== 2") # True
# Boolean operators
expr.evaluate("10 > 5 && 20 > 10") # True
expr.evaluate("10 > 50 || 20 > 10") # True
expr.evaluate("!0") # True
# Keywords
expr.evaluate("true == true") # True
expr.evaluate("null == null") # True
Strings
expr = Expression()
# String literals (single or double quotes)
expr.evaluate('"hello" + " " + "world"') # "hello world"
expr.evaluate("'foo' == 'foo'") # True
# Ruby-style string operators
expr.evaluate('"ab" * 3') # "ababab" (repeat)
expr.evaluate('"a" << "b"') # "ab" (append; mutates a variable)
expr.evaluate('"a" <=> "b"') # -1 (lexicographic compare)
Regular Expressions
expr = Expression()
# Match operator
expr.evaluate('"foobar" =~ /([fo]+)/i') # True
expr.evaluate("$1") # "Foo" (capture group)
expr.evaluate('"hello" =~ /([0-9]+)/') # False
JSON Literals
expr = Expression()
# Objects and arrays
expr.evaluate('{"name": "Alice"}') # {"name": "Alice"}
expr.evaluate('[10, 20, 30]') # [10, 20, 30]
# Property access
expr.evaluate('{"foo": "bar"}["foo"]') # "bar"
expr.evaluate('[10, 20][0]') # 10
# Comparison
expr.evaluate('{"a": 1} == {"a": 1}') # True
expr.evaluate('[1, 2] != [3, 4]') # True
Array Operators
Ruby-inspired operators for concise list manipulation. When one operand is an array and the other is a scalar, the scalar is coerced to a single-element array.
expr = Expression()
# Intersection (&) — common elements, deduped, left order preserved
expr.evaluate("[1, 1, 2, 3] & [3, 4]") # [3]
# Union (|) — combined elements, deduped
expr.evaluate("[1, 2] | [2, 3]") # [1, 2, 3]
# Difference (-) — left elements not in right
expr.evaluate("[1, 2, 2, 3] - [2]") # [1, 3]
# Concatenation (+) — keeps duplicates
expr.evaluate("[1, 2] + [2, 3]") # [1, 2, 2, 3]
# Append (<<) — mutates the left operand when it is a variable
expr.evaluate("[1, 2] << 3") # [1, 2, 3]
# Multiplication (*) — repeat with an integer
expr.evaluate("[1, 2] * 3") # [1, 2, 1, 2, 1, 2]
# Join (*) — with a string separator
expr.evaluate('["a", "b"] * "-"') # "a-b"
# Deep equality (==) and spaceship (<=>)
expr.evaluate("[1, 2] == [1, 2]") # True
expr.evaluate("[1, 2] <=> [1, 3]") # -1
# Membership (in) — array element, or substring of a string
expr.evaluate("2 in [1, 2, 3]") # True
expr.evaluate('"py" in "python"') # True
# Scalar coercion
expr.evaluate("[1, 2, 3] & 2") # [2]
expr.evaluate("1 + [2, 3]") # [1, 2, 3]
When neither operand is an array, these operators fall back to scalar
semantics: & and | are bitwise AND/OR, <</>> are bitshifts, <=>
compares numbers or strings, and +/-/* are arithmetic.
expr.evaluate("6 & 3") # 2 (bitwise AND)
expr.evaluate("6 | 1") # 7 (bitwise OR)
expr.evaluate("5 <=> 3") # 1
Empty arrays are falsy (unlike JavaScript), so they work directly in boolean contexts:
expr.evaluate("!![]") # False
expr.evaluate('[] || "default"') # "default"
expr.evaluate("[] ? 'yes' : 'no'") # "no"
# Validator pattern: true only when at least one skill matches
expr.variables = {"skills": ["Python", "AI"]}
bool(expr.evaluate('skills & ["AI", "ML"]')) # True
Conditional (Ternary) Operator
expr = Expression()
expr.evaluate("1 > 0 ? 'yes' : 'no'") # "yes"
expr.evaluate("[] ? 'yes' : 'no'") # "no"
Error Handling
expr = Expression()
# Errors raise exceptions by default
try:
expr.evaluate("10 / 0")
except ZeroDivisionError:
print("Division by zero!")
# Suppress errors (returns None on error)
expr.suppress_errors = True
expr.evaluate("unknown_var") # None
print(expr.last_error) # error message
Semicolons
Trailing semicolons are optional and ignored:
expr.evaluate("10 + 10;") # 20
Operator Precedence (lowest to highest)
| Precedence | Operators |
|---|---|
| 0 | ? : (ternary) |
| 1 | || |
| 2 | && |
| 3 | ==, !=, ===, !==, =~, <=>, >, <, >=, <=, in |
| 4 | <<, >> |
| 5 | +, -, | |
| 6 | *, /, %, &, [], implicit multiplication |
| 7 | !, unary -, unary + |
| 8 | **, ^ |
Operators & (intersection), \| (union), - (difference), +
(concatenation), << (append), * (repeat/join), == (deep equality), <=>
(spaceship), and in (membership) are type-dispatched: they apply array
semantics when either operand is an array, otherwise scalar semantics.
Development
# Install in development mode
pip install -e ".[dev]"
# Run tests
pytest
# Regenerate parser from grammar
python -m pegen expression/grammar.peg -o expression/parser.py
License
Copyright (c) 2026 Jakub T. Jankiewicz
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
See LICENSE for the full text.
Metadata
Release files for expression-py 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| expression_py-0.1.0.tar.gz | 32.6 kB | With provenance | Details for expression_py-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| expression_py-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for expression_py-0.1.0-py3-none-any.whl |
Total release size: 61.6 kB
Release files / expression_py-0.1.0.tar.gz
| Download URL | expression_py-0.1.0.tar.gz |
|---|---|
| Size | 32.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2a6b8e0af8dea2a331aea1ad31d658760d1bc1d42ed4ad5e799815fb2ad9236d
|
|
BLAKE2b-256 checksum How to use checksums |
f69df42aeb5f60fe5cfe4fc1afca4487399325b8c8199e9e28f002945a00463e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 11, 2026.
Transparency logRelease files / expression_py-0.1.0-py3-none-any.whl
| Download URL | expression_py-0.1.0-py3-none-any.whl |
|---|---|
| Size | 29.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5e6c2c3ae70242b9b28c3cadde888707a3fd65c797bc823ad6639653679c9d2e
|
|
BLAKE2b-256 checksum How to use checksums |
ddeea95560f9048a5effdc6566c7b8e46c4048f9b4487c3030dad0e8523fff1e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 11, 2026.
Transparency logSecurity for expression-py 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for expression_py-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 11, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.