Home / Alt manpages / py3compile(1)

  • py3compile(1)
  • User command
  • linux

Compile Python 3 Files Safely with py3compile

You will finish with a repeatable way to byte-compile Python 3 source files into their normal cache locations, check what was produced, and avoid turning a routine build step into a system-wide change. The examples use py3compile from python3-minimal 3.12.3-0ubuntu2.1, as installed on this machine.

Allow about ten minutes. You need a shell, a readable Python source tree, and write access to the directories where its caches will be created. Ordinary project files do not need sudo. A package-wide compile can touch many files, so run it during a suitable maintenance or build window and keep a way to identify the files it changes.

1. Check the installed command

Start with read-only checks. This confirms that you are using the Debian wrapper rather than assuming that another Python installation has the same options:

$ command -v py3compile
/usr/bin/py3compile
$ py3compile --version
py3compile 3.12.3-0ubuntu2.1
$ dpkg-query -W -f='${Package} ${Version}\n' python3-minimal
python3-minimal 3.12.3-0ubuntu2.1

Your version may differ. The important distinction is that py3compile is a Debian utility wrapping Python's py_compile module. It is not the same interface as invoking python -m py_compile or compileall. Use the options shown by the installed command when writing scripts for a particular host.

Checkpoint: if command -v finds nothing, stop and install or repair the package through your normal system-management process. Do not copy a script from another host into /usr/bin.

2. Compile one known source file

Pass a file or directory as the positional argument. This example uses a project path placeholder and compiles one module:

$ py3compile /path/to/project/app.py
$ printf 'exit status: %s\n' "$?"
exit status: 0

For a normal Python 3 installation, the cache is placed beside the source in a directory named __pycache__. Its filename includes the interpreter tag, such as app.cpython-312.pyc. Check the actual result rather than relying on a remembered suffix:

$ find /path/to/project/__pycache__ -maxdepth 1 -type f -name 'app*.pyc' -print
/path/to/project/__pycache__/app.cpython-312.pyc

The exact tag depends on the Python version installed on the machine. A bytecode file is a cache, not a replacement for the source. Keep the .py file and test the application that will consume the cache.

3. Compile a directory, then inspect the scope

Give py3compile a directory when several source files need compiling:

$ py3compile /path/to/project
$ find /path/to/project -type f -path '*/__pycache__/*.pyc' -print | sort
/path/to/project/__pycache__/app.cpython-312.pyc
/path/to/project/lib/__pycache__/helpers.cpython-312.pyc

Do not read that output as a promise that every file under the directory was compiled. Excluded paths, files without the expected source suffix, syntax errors, permissions, and the wrapper's package rules can all affect the result. Use find to review the caches that exist and compare them with the source tree you intended to process.

There is no need to run this as root merely because a cache is missing. First check ownership and permissions:

$ ls -ld /path/to/project /path/to/project/__pycache__
$ test -w /path/to/project && echo 'project directory is writable'
project directory is writable

If the directory belongs to a package or service account, run the operation as the account that owns the deployment, or use the distribution's package tooling. Broad sudo py3compile can create root-owned caches that later prevent the application user from updating them.

4. Rebuild stale caches only when needed

By default, up-to-date bytecode is left alone. Add --force when you deliberately need to rebuild even when source timestamps appear current:

$ py3compile --force /path/to/project
$ printf 'exit status: %s\n' "$?"
exit status: 0

Use this after a controlled deployment or when a filesystem has unreliable timestamps. It still writes caches into the source tree, so inspect the target first. --verbose prints diagnostic information such as the parsed arguments and selected options:

$ py3compile --verbose --force /path/to/project/app.py
D: py3compile:... argv: [...]
D: py3compile:... options: {... 'force': True, ...}

Line numbers and the full argument representation vary between versions. If the command fails, keep the diagnostic output and the exit status; do not treat a quiet terminal as proof that a cache was rebuilt.

5. Exclude files that should not be compiled

Use --exclude, or -X, with a regular expression. The expression is matched against candidate paths, and the option may be repeated. Anchor it carefully so that an exclusion does not silently cover more of the tree than intended:

$ py3compile --verbose \
    --exclude '/tests/' \
    --exclude '/__init__\.py$' \
    /path/to/project

That example is illustrative: replace the expressions with rules that match your own layout. Verify the scope by checking for a cache under a path you meant to omit, and by reviewing verbose output if the installed version reports candidate processing. A regular expression that matches a parent directory can exclude all of its children.

Do not use --exclude to hide a syntax error or permission problem. Exclude only files that are intentionally outside the build target, such as tests in a production-only image. If you later need those files, rerun without the relevant expression. No persistent configuration is changed by the command, so there is no separate undo operation.

6. Understand version selection and package mode

The -V option selects a Python 3 version range for private modules, rather than simply choosing an arbitrary interpreter. Examples from the installed manual include 3.1 for one version, 3.1- for that version or newer, 3.1-3.3 for the versions in that interval, and -4.0 for all supported 3.x versions. On a modern host, adapt the values to versions actually installed:

$ py3compile -V 3.12 /path/to/project
$ printf 'exit status: %s\n' "$?"
exit status: 0

Without other options, -V can also select public modules matching the range. That is a wider operation than compiling one project file, so inspect the command's help and the installed Python versions before using it on a system tree.

The -p or --package form names a Debian package whose files should be byte-compiled:

$ py3compile --package PACKAGE_NAME
$ printf 'exit status: %s\n' "$?"
exit status: 0

Replace PACKAGE_NAME with a real installed Debian package name. Package mode can affect files outside your project and may require write access to system directories. Treat it as an administrative operation: review the package, use the distribution's normal maintenance window, and do not guess a package name. The command has no rollback switch; removing generated caches is a separate, potentially disruptive cleanup task.

7. Diagnose failures without hiding them

Use the default output when you are investigating. --quiet suppresses normal diagnostic chatter, which is useful in a carefully designed script but a distraction during first checks. --verbose is the better first choice when you need to see how arguments were interpreted.

When no cache appears, check the input path, source readability, destination writability, exclusions, and the interpreter versions selected by -V. A command that returns successfully is evidence about that invocation, not a guarantee that every source file in a large tree was processed. Keep the source tree available while testing imports or starting the service.

Before replacing an existing deployment, copy or snapshot the relevant project using your normal release process. The bytecode cache itself is disposable, but overwriting a source tree, package ownership, or a service's files is not a safe experiment. If a test has changed only caches, remove the generated __pycache__ directory through your project's documented clean command, or restore it from the deployment artefact. Do not delete caches from a shared system tree without checking which processes and packages use them.

Done means

  • You confirmed the installed py3compile and python3-minimal versions.
  • You compiled the intended file or directory and checked the resulting __pycache__ paths.
  • You used --force only for an intentional rebuild, and understood that it changes cache files.
  • Your exclusion expressions were narrow and verified against the actual tree.
  • You treated -p and broad version ranges as administrative operations.
  • You kept source files, ownership, and the deployment rollback path intact.