The py_compile module compiles one Python source file into a .pyc bytecode cache. It is useful when an editor, incremental build, installer, generator, or validation service needs precise control over one file instead of compiling a complete directory tree.
The API can choose the output path, define the logical filename shown in tracebacks, select an optimization level, and configure how the cache is invalidated. For entire projects, compileall is usually more convenient; for file-by-file workflows, py_compile is the focused tool.
Compile a source file
The primary function is py_compile.compile().
import py_compile
pyc_path = py_compile.compile(
"module.py",
doraise=True,
)
print(pyc_path)
When compilation succeeds, the function returns the path of the generated bytecode file.
Use doraise in automation
With doraise=True, a compilation failure raises PyCompileError.
try:
py_compile.compile("module.py", doraise=True)
except py_compile.PyCompileError as error:
print(error)
This is normally the clearest behavior for CI, editors, code generators, and package builders because the caller can stop immediately and report a structured error.
Behavior without doraise
When doraise=False, the module may print the compilation message to stderr instead of raising it directly.
Always inspect the return value. An old .pyc file may still exist after a failed attempt and should not be treated as proof that the current source compiled.
PyCompileError details
PyCompileError preserves information about the original syntax or compilation exception and provides a formatted message.
A diagnostic interface should keep filename, line, column, and error type. It can retrieve a short source excerpt with Python linecache.
The default output path
If cfile is omitted, Python computes the normal cache path under __pycache__.
__pycache__/module.cpython-314.pycThe interpreter tag allows caches from different Python versions and optimization settings to coexist.
Choose a custom cfile
The cfile parameter selects a specific output file.
py_compile.compile(
"src/module.py",
cfile="build/module.pyc",
doraise=True,
)
Create the parent directory first and make sure the destination belongs to the intended build workspace.
Protect output paths
The module checks selected cases where replacing the output would be unsafe, including relevant symbolic-link and non-regular-file situations.
Applications should still validate every destination, resolve it against an approved root, and avoid accepting arbitrary output paths from external input.
Atomic cache writing
Modern Python writes the compiled data to a temporary file and then replaces the target, reducing the risk of leaving a partial cache after an interruption.
The exact guarantees depend on the filesystem. Test network volumes, container mounts, and special storage when reliability matters.
Set the logical filename with dfile
The dfile argument controls the filename stored in the code object and later displayed in tracebacks.
py_compile.compile(
"/build/work/module.py",
dfile="/opt/app/module.py",
doraise=True,
)
This is valuable when a build-worker path should not appear in production logs.
dfile does not copy source
The argument changes only the logical reference embedded in bytecode. It does not move the source or guarantee that the named path exists on the target system.
Choose a value that matches the final installation layout and preserve corresponding source when operators need readable diagnostics.
Select optimization
The optimize parameter chooses an optimization level.
py_compile.compile(
"module.py",
optimize=1,
doraise=True,
)
The default follows the interpreter’s current optimization setting.
Assertions and docstrings
Optimization level 1 removes assertions, and level 2 can also remove docstrings.
Assertions are useful for development assumptions, but mandatory input validation and application rules must use explicit checks that remain active in every build.
Cache invalidation modes
The invalidation_mode parameter controls how the import system decides whether the bytecode still matches its source.
py_compile.compile(
"module.py",
doraise=True,
invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
)
TIMESTAMP mode
TIMESTAMP records source metadata such as modification time and size.
It is efficient and commonly used, although builds that require deterministic artifacts may prefer a hash-based mode.
CHECKED_HASH mode
CHECKED_HASH stores a hash of the source and asks the import process to verify it.
This gives a stronger relationship between the content and its cache, with additional source reading according to the import policy.
UNCHECKED_HASH mode
UNCHECKED_HASH stores the source hash while allowing a managed environment to treat the artifact as already validated.
It fits controlled package systems that own installation and update decisions. Document the policy so stale caches cannot be introduced accidentally.
SOURCE_DATE_EPOCH
Reproducible-build environments may define SOURCE_DATE_EPOCH, which can influence the default invalidation behavior.
When the invalidation mode is part of the artifact contract, set it explicitly rather than relying only on environment defaults.
Compilation does not run the module
The compiler parses source and creates bytecode without executing imports, decorators, module-level calls, or class initialization.
A file can compile successfully and still fail during import because a dependency is missing or a runtime operation raises an exception.
Incremental editor validation
An editor can compile only the document that was saved.
def validate(path):
try:
py_compile.compile(path, doraise=True)
except py_compile.PyCompileError as error:
return False, str(error)
return True, None
This quickly catches syntax issues, but it does not replace a language server, linter, or type checker.
Validate generated code
Code generators can compile their output immediately to confirm that templates produced valid Python.
A reliable sequence is to write source atomically, compile it, and publish the generated package only after success.
Source encoding
The compiler follows Python’s source-encoding rules. Invalid declarations or incompatible bytes produce errors.
Use tokenize.open() when inspecting source and see Python tokenize.
Keep filenames private and useful
Absolute build paths stored in code objects can reveal usernames, CI directories, and infrastructure layout.
Use dfile to create stable production paths while retaining enough detail for debugging.
Command-line usage
The module can compile several named files from the command line.
python -m py_compile module.py other.pyCI should check the command’s exit status. Consult the active interpreter’s help for exact command-line behavior.
Compile many files
The Python function compiles one file at a time. For a complete directory tree, use Python compileall.
Incremental systems can maintain a bounded queue and associate each result with the source revision that produced it.
Parallel compilation
Independent files can be compiled concurrently, but two workers must not write the same output path.
Generate deterministic destinations, remove duplicate jobs, and keep concurrency appropriate for the filesystem.
Source changes during compilation
A file may change between scheduling and result publication. Editors should compare a document version or content hash before accepting a result.
If the version differs, discard the stale compilation and process the latest source.
Read-only application directories
When runtime directories are read-only, compile during image creation or choose a writable build destination.
A production service should not need elevated privileges merely to create __pycache__.
Version compatibility
Bytecode is tied to a Python implementation and major/minor version. A magic number prevents many incompatible loads.
Compile with the same interpreter that will run the application and do not publish one .pyc as a universal artifact.
Bytecode is not source protection
Inspection tools can recover names, constants, metadata, and instructions from compiled files.
Keep secrets outside source and bytecode, and do not rely on .pyc as strong obfuscation.
Run an import smoke test
After compilation, import the module in the target environment.
python -c "import module"This detects missing dependencies and initialization behavior that syntax compilation cannot see.
Remove orphaned artifacts
Deleted source files can leave old caches behind. A clean build directory prevents removed modules from surviving in a package.
Never mix caches from several commits, interpreters, or optimization settings without an intentional layout.
Path controls
Validate source and output paths, parent directories, and symbolic links. Keep every compilation inside a defined workspace.
A public compilation service should use a separate worker process and isolated temporary directory.
Resource limits
Very large or deeply nested source can consume substantial CPU and memory. Limit source size, concurrent jobs, and total compilation time.
Run public or uploaded-source validation away from the main request process.
Observability
Record logical filename, duration, source size, Python version, optimization level, invalidation mode, and outcome.
Avoid logging entire source files. A brief diagnostic excerpt is normally sufficient.
Testing
Test valid code, SyntaxError, invalid encoding, custom output, dfile, each invalidation mode, optimization, permission failures, symbolic-link destinations, and source changes during the job.
Verify both the generated cache path and the filename shown in a traceback.
Common mistakes
Common failures include omitting doraise=True in automation, ignoring the result, writing to an unchecked destination, compiling with a different Python version, relying on assertions for mandatory rules, confusing dfile with file copying, and leaving stale caches in builds.
Conclusion
py_compile offers precise control over compiling one Python source file. Use doraise=True, select safe cfile and dfile values, define optimization and invalidation according to build policy, and test imports in the final environment.
Use compileall for directory trees. Consult the official py_compile documentation and Python dis to inspect the resulting bytecode.







