Skip to main content

expression.py

pip CI PyPI Downloads expression.py GitHub repo Coverage Status LICENSE GPL-3.0

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)

Source distribution for expression-py 0.1.0
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)

Table of built distributions (wheels) for expression-py 0.1.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page