mathbug
A deliberately confusing Python library that swaps basic arithmetic operators at runtime by transforming the caller's source code.
import mathbug
mathbug.inject()
print(1 + 1) # 0
print(1 - 1) # 2
print(1 * 2) # 0.5
print(1 / 3) # 3
mathbug is a small experimental library for exploring Python's Abstract Syntax Tree (AST), source transformation, and runtime code execution. It does not modify Python's built-in numeric types or interpreter. Instead, it reads and transforms the code that appears after mathbug.inject().
Operator mappings
After mathbug.inject() is called, the following operators are swapped:
| Written operator | Executed as |
|---|---|
+ |
- |
- |
+ |
* |
/ |
/ |
* |
+= |
-= |
-= |
+= |
*= |
/= |
/= |
*= |
For example:
result = (10 + 4) * 2
is transformed into the equivalent of:
result = (10 - 4) / 2
The result is therefore 3.0.
Requirements
- Python 3.14 or later
- A real
.pysource file
Interactive shells, eval(), exec(), and most notebook environments are not supported because mathbug needs to read the caller's source file.
Installation
Install from the local repository
Clone the repository and install it with uv:
git clone https://github.com/swarfte/mathbug.git
cd mathbug
uv sync
To use the local package in another uv project:
uv add --editable ../path/to/mathbug
Alternatively, install it with pip:
pip install -e ../path/to/mathbug
The GitHub URL above assumes this repository is published as
swarfte/mathbug. Update it if the final repository URL is different.
Usage
Create a Python file, for example example.py:
import mathbug
mathbug.inject()
print(1 + 1)
print(1 - 1)
print(1 * 2)
print(1 / 3)
Run it:
uv run python example.py
Expected output:
0
2
0.5
3
Variables
The transformation is not limited to numeric literals:
import mathbug
mathbug.inject()
a = 10
b = 4
print(a + b) # 6
print(a - b) # 14
print(a * b) # 2.5
print(a / b) # 40
Augmented assignments
Augmented assignments are transformed as well:
import mathbug
mathbug.inject()
value = 10
value += 3
print(value) # 7
In this example, value += 3 is executed as value -= 3.
Important placement rule
Call mathbug.inject() near the beginning of the file and at module level:
import mathbug
mathbug.inject()
# Transformed code starts here.
Only top-level statements that appear after the inject() call are selected for transformation and re-execution by the current implementation.
Do not place it inside a function, loop, conditional block, or class body:
# Not supported
def main():
mathbug.inject()
How it works
A normal import cannot retroactively change operators in the same file because Python parses and compiles the module before executing its statements. For that reason, merely writing import mathbug is not enough.
When mathbug.inject() runs, it:
- Inspects the calling frame.
- Locates the caller's
.pyfile and the line containinginject(). - Reads and parses the source file into a Python AST.
- Keeps the top-level statements after the
inject()call. - Replaces
Add,Sub,Mult, andDivAST operators. - Applies the same replacements to augmented assignments.
- Compiles and executes the transformed AST in the caller's namespace.
- Stops the original execution to prevent the remaining code from running twice.
In simplified form:
source file
|
v
parse into AST
|
v
swap arithmetic operators
|
v
compile transformed AST
|
v
execute transformed code
mathbug therefore performs source transformation. It does not monkey-patch int, float, or Python's bytecode interpreter.
Limitations and surprising behavior
This project intentionally changes the apparent meaning of source code. Expect unusual behavior.
Division changes result types
4 * 2
is transformed into:
4 / 2
The result is 2.0, not 2.
Multiplication by zero may fail
10 * 0
becomes:
10 / 0
and raises ZeroDivisionError.
Operators on strings and collections may fail
Valid Python such as:
"bug" * 3
[1, 2] + [3, 4]
becomes equivalent to:
"bug" / 3
[1, 2] - [3, 4]
which raises TypeError.
Imported libraries are not rewritten
mathbug transforms the selected statements in the caller's source file. It does not rewrite Python's standard library or every installed dependency.
Tooling may be misleading
Editors, linters, type checkers, debuggers, coverage tools, and code reviewers see the operators that were written, not necessarily the operations eventually executed.
The process exits after transformed execution
The current implementation raises SystemExit(0) after executing the transformed code. Applications that need to intercept or manage process termination may require a different integration approach.
Intended use
mathbug is intended for:
- learning about Python AST transformations;
- experimenting with runtime code rewriting;
- programming puzzles;
- demonstrations and educational examples;
- harmless joke programs in controlled environments.
It is not intended for:
- production applications;
- safety-critical calculations;
- financial, medical, scientific, or security-sensitive software;
- code that must remain easy to audit and debug;
- modifying third-party programs without their users' knowledge.
Development
Set up the environment
uv sync
Run the example
uv run python example.py
Run the tests
If pytest has not been added yet:
uv add --dev pytest
Then run:
uv run pytest
Project structure
mathbug/
|-- src/
| `-- mathbug/
| |-- __init__.py
| |-- _injector.py
| `-- py.typed
|-- test/
| |-- fixtures/
| | `-- basic_operations.py
| `-- test_mathbug.py
|-- LICENSE
|-- pyproject.toml
|-- README.md
`-- uv.lock
Build the package
Build both the source distribution and wheel:
uv build
The generated files will be placed in dist/.
To verify that the package can be built without local source overrides:
uv build --no-sources
You can inspect the generated artifacts with:
uv run python -m zipfile -l dist/*.whl
On PowerShell, if wildcard handling causes a problem, replace dist/*.whl with the exact wheel filename.
Public API
The public API currently contains one function:
mathbug.inject()
Internal implementation details live in mathbug._injector and may change between releases.
Contributing
Bug reports, tests, documentation improvements, and focused pull requests are welcome.
When contributing:
- Create a branch for the change.
- Add or update tests where appropriate.
- Run
uv run pytest. - Confirm that
uv build --no-sourcessucceeds. - Open a pull request describing the behavior and motivation.
License
This project is licensed under the terms included in the LICENSE file.
Disclaimer
This library deliberately makes Python code behave differently from how it appears. Use it only in environments where all participants understand what it does. The author is not responsible for incorrect calculations, broken programs, confused coworkers, or arithmetic-related existential crises.
Metadata
Release files for mathbug 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mathbug-0.1.1.tar.gz | 5.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mathbug-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 11.1 kB
Release files / mathbug-0.1.1.tar.gz
| Download URL | mathbug-0.1.1.tar.gz |
|---|---|
| Size | 5.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eb79f091cb61caa86cd12e321cc762d064408b7981b0b144afd39a66c425beda
|
|
BLAKE2b-256 checksum How to use checksums |
75c3943ffd2f137ff1a46dbad6a5ff13121865662e391c599c8507de1d65a72b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","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}
|
Release files / mathbug-0.1.1-py3-none-any.whl
| Download URL | mathbug-0.1.1-py3-none-any.whl |
|---|---|
| Size | 6.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8f183893659df3cd62fd79c472517fbb67ccffcda94ff4bbda143f39b288d515
|
|
BLAKE2b-256 checksum How to use checksums |
223f5ee85b53add51147258ad2fe095682d2251ea8a19ab6bfb11c84664ae998
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","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}
|