Python compileall: Generate .pyc Bytecode

Published on: August 27, 2026
Reading time: 7 minutes
A developer typing code on a laptop with a Python book beside in an office.

The compileall module compiles Python source files across a directory tree into .pyc bytecode. It is useful in build pipelines, container images, system packages, offline deployments, syntax validation, and read-only environments where the first import should not need to create caches.

The module does not turn Python into a standalone executable and does not protect source code. Bytecode remains tied to a compatible Python implementation and version, can be inspected, and normally acts only as an import cache. Use compileall for operational preparation and verification, not secrecy.

Compile a directory

compile_dir() walks a directory and compiles recognized source files.

import compileall

success = compileall.compile_dir(
    "my_package",
    quiet=1,
)
print(success)

The overall result indicates whether requested files compiled successfully. Treat False as a build failure.

Compile one file

compile_file() targets a single path.

compileall.compile_file(
    "my_package/module.py",
    quiet=1,
)

For lower-level control over a single file, use py_compile, which is covered in the next article.

Use the command line

The module provides a CLI.

python -m compileall my_package

This is convenient in Dockerfiles and CI. Check the exit status instead of relying only on printed output.

Where .pyc files are stored

Modern Python normally writes caches into __pycache__ directories, using filenames that include the interpreter tag and optimization level.

my_package/__pycache__/module.cpython-314.pyc

This allows caches for several versions or optimization settings to coexist.

Legacy layout

With legacy=True, compiled files are written beside the source using the historical naming convention.

Avoid this unless a legacy tool requires it. The __pycache__ layout is the normal choice for modern import machinery.

Force recompilation

force=True compiles files even when existing caches appear current.

compileall.compile_dir(
    "my_package",
    force=True,
    quiet=1,
)

This helps reproducible builds and policy changes, but increases build time and disk writes.

Syntax validation

Compiling a tree detects SyntaxError, indentation failures, and selected version incompatibilities without executing module bodies.

It is a fast release check, but it does not replace tests, real imports, type checking, or linting.

Compilation does not execute modules

The compiler parses source and creates code objects. It does not run imports, decorators, top-level calls, or class initialization.

A module can compile successfully and fail at import because of a missing dependency, environment variable, runtime error, or incompatible extension.

Directory recursion

compile_dir() can limit recursion depth.

compileall.compile_dir(
    "src",
    maxlevels=5,
    quiet=1,
)

Set a limit when the root contains mounts, generated trees, or unexpected links.

Evaluate link behavior in the real deployment environment. A build tree may contain symlinks that escape the root or duplicate large directories.

Compile explicit trusted roots and never accept an arbitrary user-controlled path without validation.

Exclude paths with rx

The rx parameter accepts a regular expression for excluded paths.

import re

compileall.compile_dir(
    "project",
    rx=re.compile(r"/(tests|vendor)/"),
    quiet=1,
)

Test expressions on Windows and Unix because separators and path representations differ.

Quiet output

quiet controls ordinary messages. Automation can reduce noise while preserving errors.

Silence is not observability. Record duration, file counts, interpreter version, and final status.

Parallel workers

workers compiles several files concurrently.

compileall.compile_dir(
    "src",
    workers=4,
    quiet=1,
)

Choose a value that respects CPU, filesystem, and CI-runner limits. More workers can reduce performance on slow or shared storage.

Automatic worker counts

Supported versions may accept special values that derive a worker count from the system. Verify the documentation for the exact interpreter.

Explicit limits are often more predictable in controlled builds.

Optimization levels

optimize selects default, -O, or -OO compilation. Recent APIs can accept multiple levels.

compileall.compile_dir(
    "src",
    optimize=[0, 1, 2],
    quiet=1,
)

This can create several cache files for each source module.

What -O changes

Optimized mode removes assertions and makes __debug__ false. -OO can also remove docstrings.

Never use assert for input validation, access control, or production-critical invariants.

When several optimization levels produce identical data, hardlink_dupes can save space with hard links on supported filesystems.

Test this in containers, package builders, volumes, and backup systems because hard-link support and semantics vary.

Invalidation modes

A .pyc file uses a cache-invalidation policy based on timestamps or hashes.

Reproducible builds often prefer hash-based invalidation so variable mtimes do not define artifact identity. Use py_compile.PycInvalidationMode when calling the API.

Timestamp versus hash

Timestamp mode is fast and common but depends on source size and modification time. Hash mode embeds source identity and is better suited to hermetic artifacts.

Choose a policy that matches installation and import behavior.

SOURCE_DATE_EPOCH

Reproducible-build systems may set SOURCE_DATE_EPOCH, influencing deterministic defaults and metadata.

That variable alone is not enough. Also control file order, embedded paths, permissions, and interpreter version.

Filenames embedded in code objects

Code objects retain filenames displayed in tracebacks. An absolute build-worker path can leak infrastructure details and will not exist on the target machine.

Transform paths so tracebacks correspond to the installed layout.

stripdir

stripdir removes a prefix from the filename stored in code objects.

compileall.compile_dir(
    "/build/work/src",
    stripdir="/build/work",
    quiet=1,
)

Verify that the prefix matches and inspect resulting tracebacks.

prependdir

prependdir adds a prefix after stripping.

compileall.compile_dir(
    "/build/work/src",
    stripdir="/build/work/src",
    prependdir="/opt/app",
    quiet=1,
)

This aligns compiled filenames with the final installation path.

Historical directory parameters

Older destination-directory options exist for compatibility. Prefer current strip and prepend controls when available, and document the minimum Python version.

Do not combine incompatible options without checking the active signature.

Container builds

Compile after copying application code, using the same interpreter that will run it.

RUN python -m compileall -q /opt/app

Compiling in one stage and running another Python version can produce ignored or incompatible caches.

Do not copy local caches

Developer-machine caches may use another version, optimization, path layout, or invalidation policy.

Ignore __pycache__ in version control and generate bytecode in the final build environment.

Read-only deployments

Precompilation is useful when application directories are read-only and runtime users cannot create caches.

Confirm that every needed source file was compiled and that the interpreter matches.

Permissions

The build user needs permission to read source and create cache directories. Set final ownership and permissions for the runtime account.

Do not run the application as root merely to create bytecode.

Installed packages

Installers may already compile packages. Avoid repeating the work without a reason.

Use explicit compileall when policy requires syntax validation, selected optimization levels, reproducibility, or a read-only image.

Namespace packages

Namespace packages can span several directories. Compile each installed root and do not assume one directory contains the whole package.

Compilation does not validate runtime namespace composition.

Read errors

Unreadable files, broken paths, and encoding problems can fail. Preserve diagnostics and fail the build when required code does not compile.

Do not ignore a false result simply because most caches were produced.

Generated source

Generate code before compileall. If a later step changes source, caches become stale or are rebuilt at first import.

A clear pipeline is generation, formatting, validation, compilation, packaging.

Untrusted input

Do not compile user-uploaded trees in the main service process. Parsing can consume CPU and memory, while links and mounts may escape intended boundaries.

Use an isolated worker, temporary directory, file policy, and strict limits.

Bytecode is not secret

Shipping only .pyc makes casual reading less convenient, but tools can inspect constants, names, and instructions.

Never place secrets in code and do not claim strong intellectual-property protection from bytecode.

Compatibility

Bytecode formats change across Python versions. A .pyc contains a magic number and is normally rejected by incompatible interpreters.

Compile with the same implementation and major/minor version used at runtime.

Inspect with dis

The dis module shows code-object instructions and helps compare optimization levels.

See Python dis.

Validate imports

After compilation, run import smoke tests in a clean environment to detect missing dependencies and runtime initialization problems.

python -c "import my_package"

CI example

python -m compileall -q -f src
pytest

Use the interpreter version declared by the project.

Cleanup

Remove old caches by deleting __pycache__ directories inside a validated project root.

Never perform recursive deletion from an unchecked path or follow links outside the workspace.

Test the build process

Test empty trees, syntax errors, unreadable files, multiple optimization levels, transformed filenames, read-only execution, and imports on the target image.

Compare artifact hashes when reproducibility is required.

Common mistakes

Common failures include treating bytecode as a standalone executable, compiling with another version, ignoring a false result, copying local caches, relying on assertions in production, embedding build paths, compiling before generated files are final, and assuming .pyc protects code.

Conclusion

compileall prepares Python trees for imports, validates syntax, and controls optimization, concurrency, filenames, and invalidation. Run it during builds with the same interpreter as production and treat every failure as an artifact problem.

Generate caches in a clean environment, preserve useful tracebacks, and validate imports afterward. Consult the official compileall documentation and Python sysconfig for build details.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Vivid close-up of code on a computer screen showcasing programming details.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python codeop: Compile Interactive Input

    Learn Python codeop to detect complete, incomplete, or invalid commands, build REPLs, and preserve __future__ flags safely per session.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python linecache: Read Source Lines

    Learn Python linecache to retrieve source lines, refresh cached files, support tracebacks and loaders, preserve indentation, and secure paths.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python faulthandler: Diagnose Crashes

    Learn Python faulthandler to diagnose crashes, deadlocks, fatal signals, timeouts, and hangs with stack dumps from every thread.

    Ler mais

    Tempo de leitura: 8 minutos
    27/08/2026
    Detailed view of programming code in a dark theme on a computer screen.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python symtable: Analyze Name Scopes

    Learn Python symtable to analyze scopes, locals, globals, parameters, imports, nonlocals, closures, and compiler namespaces.

    Ler mais

    Tempo de leitura: 9 minutos
    27/08/2026
    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python dis: Understand Bytecode

    Learn Python dis to inspect bytecode instructions, jumps, stack effects, adaptive caches, and optimizations without relying on unstable internals.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python tokenize: Read Source Code Tokens

    Learn Python tokenize to read tokens, comments, encodings, indentation, and positions, then transform and rebuild source safely.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026