# simpleeval evaluates one expression without handing over eval()

> simpleeval is a single-file Python library that parses a user supplied expression with the ast module and evaluates only the node types you allow. It keeps the sandbox narrow by default, and its one documented weakness is that a hostile expression can still burn CPU or memory.

**danthedeckie/simpleeval** — Simple Safe Sandboxed Extensible Expression Evaluator for Python

- Repository: https://github.com/danthedeckie/simpleeval
- Stars: 615 · Forks: 97
- Language: Python
- License: NOASSERTION
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/danthedeckie-simpleeval

## One expression, many kinds of user input

The problem simpleeval addresses is specific and common. You want a user to be able to set an alarm volume that depends on the time of day, the alarm level, how many previous alarms went off and whether music is playing. Or you want a web application to accept simple formulae without giving full eval() access, or without running the calculation in JavaScript on the client side.

What you get is a library that stays deliberately small. The documentation describes it as a single file library for easily adding evaluatable expressions into Python projects, and says it is deliberately trying to stay simple to use, not have millions of features, be pullable from PyPI through pip or easy_install, or even just a single file you can dump into a project.

That last option is unusual and worth taking seriously. The whole implementation lives in simpleeval.py at the repository root, and the build configuration ships it as a single package in both the sdist and the wheel. If you are maintaining software that has to keep working in an environment where adding a dependency is a negotiation, this is the kind of library you copy in.

The audience is Python developers who need exactly one capability done with a small attack surface, not people building a general purpose scripting platform.

## The ast module is the sandbox boundary, not a regex filter

The mechanism is stated plainly in the documentation: internally, simpleeval uses the Python ast module to parse the expression, which allows very fine control of what is and isn't allowed, and it should be completely safe in terms of what operations can be performed by the expression.

That distinction matters. A filter that scans a string for dangerous substrings can be fooled by string construction, while an AST walk sees the actual structure of what was parsed. Because the parser produces a tree of node types, the evaluator can visit that tree and decide, node by node, which operations are permitted and which names may be looked up.

The default behaviour is deliberately restricted. The documentation describes the defaults as pretty locked down and basic, and says it is easy to add whatever extra specific functionality you need, such as your own functions or variable and name lookup.

The operator set is the visible part of that design. The defaults cover arithmetic, comparison, shifts, bitwise operations and the in operator, and you can add operators yourself through the operators argument. The set is broad enough for real formulae but contains nothing that touches the filesystem, the network or the interpreter internals.

## Calling it, and passing in your own functions

The simplest call takes an expression string and returns a value:

```python
    from simpleeval import simple_eval

    simple_eval("21 + 21")
```

That returns 42. Expressions can be as convoluted as you like, and the documentation gives one that mixes division, modulus and exponentiation, which returns 535.714285714.

Adding your own functions is a dictionary argument, and the example uses a lambda:

```python
    simple_eval("square(11)", functions={"square": lambda x: x*x})
```

That returns 121. The important detail is that the function must be handed to the evaluator explicitly. Nothing is resolved from the calling module's namespace, so an expression cannot reach a function you did not offer.

The documentation also points out a naming trap. The caret operator is often mistaken for an exponent operator, when in Python it is the bitwise exclusive OR. If you want 3 ^ 2 to equal 9, you can swap the operator mapping, using the safe_power helper:

```python
    >>> import ast
    >>> from simpleeval import safe_power

    >>> s = SimpleEval()
    >>> s.operators[ast.BitXor] = safe_power

    >>> s.eval("3 ^ 2")
    9
```

The SimpleEval class is the object-oriented entry point here, and it exposes the operator mapping for modification, which is how you go beyond the defaults.

## Exponentiation is capped at MAX_POWER by default

The one built-in defence against runaway expressions is a limit on the power operator. The documentation states that the power operator has been locked down by default to have a maximum input value of 4000000, which makes it somewhat harder to make expressions which go on for ever, and that you can change this limit by changing the simpleeval.MAX_POWER module level value to whatever is appropriate for you and the hardware you are running on.

That is the whole mechanism, and it is worth understanding what it does and does not cover. It bounds the exponent, so a power of 9 with an exponent of 4000000 is refused, which removes the most obvious way to force a huge computation. It does not bound the size of a nested expression, the length of a string operation, or the number of iterations a user can describe with arithmetic alone.

The library also has a separate protection against expressions that require an awful lot of memory, and the documentation notes that you can lock it down further if you desire, referring to the Operators section.

None of this is a substitute for a request timeout. If your service accepts expressions from strangers, the expression guard is one layer and your own request handling is the other.

## The documented weakness is resource exhaustion, and it is real

The documentation is unusually honest about the limitation. It states that the only issue the author knows of is that you can create an expression which takes a long time to evaluate, or which evaluating requires an awful lot of memory, which leaves the potential for DOS attacks, that there is basic protection against this, and that you can lock it down further if you desire.

It then adds the sentence that matters for deployment decisions: you should be aware of this when deploying in a public setting.

That is the honest characterisation of any expression sandbox in any language. An evaluator that forbids attribute access and imports can still be asked to compute something enormous, because arithmetic on large integers is exactly what the interpreter is good at. Guarding the operator set controls what an expression can say, not how long it may take to say it.

The second limitation is scope. This is an expression evaluator, not a statement evaluator. There is no assignment, no control flow, no function definition, and no way to call something you did not register in the functions dictionary. For a rule engine, a configuration language or a user-facing formula field, that narrowness is the feature. For anything resembling a scripting language, it means you need a different tool.

Compare that with the alternative of just calling eval. eval gives an expression full access to the interpreter, including imports and attribute traversal, so a user input string becomes a code execution path. simpleeval removes that path by never evaluating arbitrary node types. The difference between the two is the difference between an expression language and no expression language at all.

## No dependencies, MIT, and a stable 1.0 line

The packaging is unusually lean. The project metadata declares dependencies = [], an empty list, so installing simpleeval pulls in nothing else. The current version is 1.0.8 and requires-python is >=3.9, and the version badge covers Python 3.9 through 3.15 plus PyPy3.9 through PyPy3.11.

The build system is hatchling, and the dev dependency group is tooling only: hatch, mypy, ruff, tox and twine. The build targets confirm the single file layout, with packages set to simpleeval.py for the wheel and an sdist that includes simpleeval.py, the tests directory, README.rst and LICENCE.

The Makefile shows the working commands. The test target runs the suite with warnings promoted to errors:

```make
test:
	python -Werror -m unittest
```

Linting runs ruff and mypy over the module and the tests, and the dist target builds with hatch and checks the artifacts with twine.

On licensing there is a small inconsistency worth checking. The repository carries a LICENCE file and the project metadata declares license = MIT with the classifier for the MIT License, while GitHub classifies the repository itself as a custom licence it cannot categorise. The metadata is unambiguous, but if you are shipping this commercially, read the LICENCE file in the repository rather than relying on the classifier.

Upkeep looks healthy. The last push was on 2026-10-21, and the release history shows 1.0.8 on 2026-09-12, 1.0.7 on 2026-03-16 and 1.0.6 on 2026-03-13. The project metadata even classifies the package as Development Status:: 5 - Production/Stable. The code lives in one file, which cuts both ways: there is very little to break, and also very little that can be fixed without touching the same file you copied into your project.

## Conclusion

simpleeval suits a Python application that needs one safe expression, such as a user configured formula, an alarm threshold or a pricing rule, and does not want to expose eval(). It is the wrong tool for a scripting language, for anything that needs statements rather than a single expression, and for any endpoint where an attacker can submit expressions at will, because the documentation states plainly that an expression can be crafted to take a long time or consume a lot of memory. Before adopting it, read the Operators section for how far the defaults go, set simpleeval.MAX_POWER to a value your hardware can survive, and pin the version you tested against, since 1.0.8 is the current release and the last push was on 2026-10-21.

## FAQ

### How do I evaluate a user supplied expression safely in Python with simpleeval?

Import simple_eval and pass the expression string, for example simple_eval("21 + 21"), which returns 42. The library parses with the ast module and evaluates only permitted node types, so it does not need eval access.

### Can I add my own functions to simpleeval?

Yes. Pass them in the functions argument, for example simple_eval("square(11)", functions={"square": lambda x: x*x}), which returns 121. Only the functions you supply are reachable from the expression.

### What are the security limits of simpleeval?

The documentation states that an expression can be created which takes a long time to evaluate or requires a lot of memory, leaving potential for DOS attacks, and that you should be aware of this when deploying in a public setting. The power operator is limited by default through the MAX_POWER module value, and you can tighten the configuration further.

## Sources

- [danthedeckie/simpleeval on GitHub](https://github.com/danthedeckie/simpleeval)
- [Issues](https://github.com/danthedeckie/simpleeval/issues)
- [README](https://github.com/danthedeckie/simpleeval/blob/main/README.md)
- [Releases](https://github.com/danthedeckie/simpleeval/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/danthedeckie-simpleeval
