Clear Out Stale Python 3 Bytecode with py3clean
Uninstall a Debian Python package and its old .pyc files often stay behind, which is exactly the mess py3clean is built to clean up. It removes generated .pyc and .pyo files while leaving the source that produced them untouched. Along the way you will see how it picks a Python version and how package mode narrows what it touches. Allow ten minutes for a project directory, longer if you want to review a system package first.
The route
Jump straight to the step you need, or tick off Done means at the end.
Warning
Py3clean deletes files. The bytecode is normally reproducible, but deletion still is not an undoable edit. Back up anything you cannot regenerate, and inspect the target before running it. Do not point it at a broad system path as root on a first try.
1. Check the installed command
This guide covers the py3clean shipped by Ubuntu's python3-minimal package, version 3.12.3-0ubuntu2.1. Option details and file-selection behaviour can change between releases, so check your own version before wiring this into a script:
$ command -v py3clean
/usr/bin/py3clean
$ dpkg-query -W -f='${Package} ${Version}\n' python3-minimal
python3-minimal 3.12.3-0ubuntu2.1
$ py3clean --version
py3clean 3.12.3-0ubuntu2.1
$ py3clean --help
Usage: py3clean [-V VERSION] [-p PACKAGE] [DIR_OR_FILE]
The positional argument is a source directory or a Python file. Passing it the name of a bytecode file instead is a common mistake: py3clean looks for source files and removes their generated companions, not the other way round.
2. Clean one project directory
Build a disposable fixture first so you can see exactly what gets removed. These commands create files under /tmp and touch nothing in your real project:
$ demo=/tmp/py3clean-demo
$ mkdir -p "$demo/sub"
$ printf 'value = 1\n' > "$demo/main.py"
$ printf 'value = 2\n' > "$demo/sub/helper.py"
$ touch "$demo/main.pyc" "$demo/main.pyo" "$demo/sub/helper.pyc"
$ find "$demo" -type f -printf '%P\n' | sort
main.py
main.pyc
main.pyo
sub/helper.py
sub/helper.pyc
Run py3clean on the directory. -v turns on diagnostics, including a count of removed files, and it needs no special privileges when you own the directory:
$ py3clean -v "$demo"
I: py3clean:155: removed files: 3
Check the result: sources stay, the classic bytecode files are gone.
$ find "$demo" -type f -printf '%P\n' | sort
main.py
sub/helper.py
In a real tree, py3clean also considers version-tagged files inside __pycache__, such as module.cpython-312.pyc, and it can remove an empty __pycache__ directory as part of the cleanup. It never removes the .py file itself.
3. Limit removal to one Python version
Reach for -V VERSION when several Python versions share a tree and you want to clear only one version's tagged cache files. The value is a Python version, not a package name; -V 3.12 selects the cpython-312 magic tag on this installation:
$ mkdir -p "$demo/versions/__pycache__"
$ printf 'value = 3\n' > "$demo/versions/module.py"
$ touch "$demo/versions/py-only.pyc"
$ touch "$demo/versions/__pycache__/module.cpython-312.pyc"
$ touch "$demo/versions/__pycache__/module.cpython-311.pyc"
$ py3clean -V 3.12 -v "$demo/versions"
D: py3clean:78: magic tags to remove: cpython-312
I: py3clean:155: removed files: 1
$ find "$demo/versions" -type f -printf '%P\n' | sort
__pycache__/module.cpython-311.pyc
module.py
py-only.pyc
The untagged py-only.pyc and the Python 3.11 cache both survive this version-filtered run. Without -V, the command removes every Python 3 bytecode file it finds, so use the filter whenever another interpreter still needs its cache.
4. Clean files belonging to a Debian package
-p PACKAGE asks Debian's package database which files belong to a package, then cleans those. It is handy after removing a package's caches, but it is a system-wide operation: read the package name carefully and expect to need elevated privileges if the files are not yours to write.
$ dpkg-query -L python3-minimal | sed -n '1,12p'
/.
/usr
/usr/bin
/usr/bin/py3clean
/usr/bin/py3compile
/usr/share
/usr/share/doc
/usr/share/doc/python3-minimal
/usr/share/man
/usr/share/man/man1
/usr/share/man/man1/py3clean.1.gz
$ py3clean -p python3-minimal -v
Do not reach for sudo automatically. Confirm the package first and check what it owns with dpkg-query -L. If it reports permission errors on a system package, decide whether the cleanup is actually necessary before running the reviewed command again with sudo, ideally in a maintenance window.
You can combine -p with a directory or file, and py3clean will only touch files that are both under that path and recorded as belonging to the package. That intersection narrows a package cleanup, but it is not a preview: it still deletes.
5. Protect useful output and handle failures
There is no restore command built in. If a generated file matters to you, copy or package the source tree before cleaning. Python recreates import bytecode when it has permission and chooses to write it, but a read-only deployment or a missing build step can make that assumption fail badly.
For a project, verify the tree and keep a rollback copy outside the target first:
$ test -d /path/to/project
$ find /path/to/project -type f \( -name '*.py' -o -name '*.pyc' -o -name '*.pyo' \) -print
$ cp -a /path/to/project /path/to/project.before-py3clean
$ py3clean -v /path/to/project
$ python3 -m compileall -q /path/to/project
That last command is a separate verification step, not part of py3clean. It recompiles readable Python files and may need write access to the project. If the cleanup went wrong, restore the backup through your normal deployment process, and do not delete it until imports and application tests pass.
If nothing gets removed, that can be perfectly correct: there may be no Python source under the path, the package may own no bytecode there, or a version filter may exclude every cache. Recheck the path, package name and cache tags before reaching for elevated permissions.
Done means
- Confirmed the version. You checked the installed py3clean version and read its option syntax.
- Inspected before deleting. You reviewed the target directory or Debian package before running anything destructive.
- Kept privileges minimal. Project cleanup stayed unprivileged;
-pon system files was treated as elevated work. - Filtered by version when needed. You used
-Vto leave another interpreter's cache alone. - Checked the aftermath. Source files and application checks still pass after cleanup.
- Kept a safety net. A recovery copy exists for anything that could not be safely regenerated.