Skip to main content

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 .py source 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:

  1. Inspects the calling frame.
  2. Locates the caller's .py file and the line containing inject().
  3. Reads and parses the source file into a Python AST.
  4. Keeps the top-level statements after the inject() call.
  5. Replaces Add, Sub, Mult, and Div AST operators.
  6. Applies the same replacements to augmented assignments.
  7. Compiles and executes the transformed AST in the caller's namespace.
  8. 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:

  1. Create a branch for the change.
  2. Add or update tests where appropriate.
  3. Run uv run pytest.
  4. Confirm that uv build --no-sources succeeds.
  5. 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)

Source distribution for mathbug 0.1.1
File Size Uploaded
mathbug-0.1.1.tar.gz 5.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mathbug 0.1.1
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

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