A lightweight wrapper around numexpr that adds support for registered callables and automatic type normalization
Project description
funcexpr
A lightweight wrapper around numexpr that adds support for registered callables and automatic type normalization.
import numpy as np
import funcexpr as fe
a = np.array([1.0, 2.0, 3.0])
b = np.array([4.0, 5.0, 6.0])
def clip_positive(x):
return np.clip(x, 0, None)
result = fe.evaluate("clip_positive(a - b) + b", ctx={"a": a, "b": b}, funcs={"clip_positive": clip_positive})
# array([4., 5., 6.])
Motivation
numexpr is fast, but it only accepts plain ndarrays and supports a limited set of built-in functions. funcexpr solves this by pre-processing the expression AST before handing it off to numexpr:
- Registered callables are evaluated eagerly via NumPy and replaced with temporary variables before the expression reaches numexpr
- numexpr built-ins (
sin,cos,exp, etc.) at the top level of an expression are passed through to numexpr as-is; when they appear as arguments to a registered callable, they are resolved via NumPy during eager argument evaluation - Type normalization handles Python scalars, numpy scalars, and any object implementing
__array__
Installation
pip install funcexpr
Usage
Basic
import numpy as np
import funcexpr as fe
a = np.array([1.0, 2.0, 3.0])
b = np.array([4.0, 5.0, 6.0])
fe.evaluate("a + b * 2", ctx={"a": a, "b": b})
# array([ 9., 12., 15.])
Registered callables
Any Python callable can be registered via funcs. Arguments are evaluated eagerly before the callable is invoked, so nested calls and expressions as arguments work naturally.
def double(x):
return x * 2
fe.evaluate("double(a) + b", ctx={"a": a, "b": b}, funcs={"double": double})
# array([ 6., 9., 12.])
Nested calls:
fe.evaluate("double(double(a))", ctx={"a": a}, funcs={"double": double})
# array([ 4., 8., 12.])
Expression as argument:
fe.evaluate("double(a + b)", ctx={"a": a, "b": b}, funcs={"double": double})
# array([10., 14., 18.])
numexpr built-ins
numexpr built-ins used at the top level of an expression are passed through to numexpr directly and require no registration.
fe.evaluate("sin(a) + cos(b)", ctx={"a": a, "b": b})
When a built-in appears as an argument to a registered callable, it is resolved via NumPy during eager argument evaluation.
def my_func(x):
return x * 2
fe.evaluate("my_func(sin(a))", ctx={"a": a}, funcs={"my_func": my_func})
# equivalent to my_func(np.sin(a))
Type normalization
ctx values and callable return values are normalized automatically.
| Type | Behavior |
|---|---|
int, float, complex |
passed through as-is |
np.generic (numpy scalar) |
passed through as-is |
np.ndarray |
passed through as-is |
object with __array__ |
converted via np.asarray() |
| anything else | TypeError |
API reference
def evaluate(
expr: str,
ctx: dict[str, np.ndarray | int | float | complex | np.generic],
funcs: dict[str, Callable] | None = None,
) -> np.ndarray:
| Parameter | Type | Default | Description |
|---|---|---|---|
expr |
str |
— | Python expression string to evaluate |
ctx |
dict |
— | Variable context |
funcs |
dict[str, Callable] | None |
None |
Registered callables |
Returns np.ndarray.
Error handling
| Condition | Exception |
|---|---|
| Invalid expression | SyntaxError |
Variable missing from ctx |
KeyError (raised by numexpr) |
Unnormalizable type in ctx or callable return value |
TypeError |
| Unrecognized function name | TypeError (raised by numexpr) |
| Any other numexpr or callable error | propagated as-is |
Limitations
Callable arguments support a subset of AST node types: Constant, Name, BinOp, UnaryOp, and Call. Passing unsupported node types (e.g. Compare, BoolOp, IfExp) as callable arguments will raise TypeError. This may be lifted in a future version.
Design
funcexpr is intentionally minimal. It does one thing: let you use arbitrary callables inside numexpr expressions. More advanced features such as xarray DataArray support and axis alignment are out of scope and belong in a higher-level layer.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file funcexpr-0.1.2.tar.gz.
File metadata
- Download URL: funcexpr-0.1.2.tar.gz
- Upload date:
- Size: 8.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e81192773ff34d33a3f1dbae044c299f2a200e4630111e15fae1636f8dece88
|
|
| MD5 |
9d70a3dbe66aee6f2b70c55b3eb4909a
|
|
| BLAKE2b-256 |
275192c5f62c48143b35f0e974f5c600112f962359768881f52cda1ebf99252a
|
File details
Details for the file funcexpr-0.1.2-py3-none-any.whl.
File metadata
- Download URL: funcexpr-0.1.2-py3-none-any.whl
- Upload date:
- Size: 8.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0937fcc18cedd8407f29f3a1cb6c50524ace843c3f47f5c0a3c39aaf0c5b588a
|
|
| MD5 |
721bf863cee9b7b006588b0cea8eae6e
|
|
| BLAKE2b-256 |
65167c537860d7482a9b7e91e8c733813969e3cbca4d0013d799ef27e77005cd
|