El módulo compileall compila archivos fuente Python de un directorio o árbol completo en bytecode .pyc. Es útil en pipelines de build, imágenes de containers, paquetes de sistema, despliegues offline, validación de sintaxis y entornos read-only donde la primera importación no debería crear caches.
El módulo no convierte Python en un ejecutable independiente y no protege el código. El bytecode depende de una implementación y versión compatible, puede inspeccionarse y normalmente actúa como cache de importación. Usa compileall para preparación operativa y verificación, no para secreto.
Compila un directorio
compile_dir() recorre un directorio y compila archivos reconocidos.
import compileall
exito = compileall.compile_dir(
"mi_paquete",
quiet=1,
)
print(exito)
El resultado general indica si todas las compilaciones solicitadas terminaron correctamente. En un build, trata False como fallo.
Compila un archivo
compile_file() procesa un path individual.
compileall.compile_file(
"mi_paquete/modulo.py",
quiet=1,
)
Para control de bajo nivel sobre un único archivo, utiliza py_compile, que se aborda en el siguiente artículo.
Usa la línea de comandos
El módulo ofrece una CLI.
python -m compileall mi_paqueteEs cómoda en Dockerfiles y CI. Comprueba el exit code y no dependas solo del texto impreso.
Dónde se guardan los .pyc
Python moderno normalmente escribe caches en directorios __pycache__, con nombres que incluyen la tag del intérprete y el nivel de optimización.
mi_paquete/__pycache__/modulo.cpython-314.pycAsí pueden coexistir caches de varias versiones y optimizaciones.
Layout legacy
Con legacy=True, los archivos compilados se escriben junto al fuente con la convención antigua.
Evita este modo salvo que una herramienta legacy lo exija. __pycache__ es el layout normal del sistema de importación moderno.
Fuerza recompilación
force=True compila aunque el cache parezca actualizado.
compileall.compile_dir(
"mi_paquete",
force=True,
quiet=1,
)
Es útil para builds reproducibles y cambios de política, pero aumenta tiempo y escrituras.
Validación de sintaxis
Compilar un árbol detecta SyntaxError, indentación inválida y ciertas incompatibilidades de versión sin ejecutar el cuerpo de los módulos.
Es una comprobación rápida, pero no sustituye tests, imports reales, type checking o linting.
La compilación no ejecuta módulos
El compilador analiza y crea code objects. No ejecuta imports, decorators, llamadas de nivel superior o inicialización de clases.
Un módulo puede compilar y fallar al importar por dependencia ausente, variable de entorno, error de runtime o extensión incompatible.
Recursión de directorios
compile_dir() permite limitar profundidad.
compileall.compile_dir(
"src",
maxlevels=5,
quiet=1,
)
Define límites cuando la raíz contiene mounts, árboles generados o enlaces inesperados.
Enlaces simbólicos
Evalúa su comportamiento en el entorno real. Un árbol de build puede contener symlinks que salen de la raíz o duplican directorios.
Compila raíces confiables explícitas y nunca aceptes un path arbitrario sin validación.
Excluye paths con rx
El parámetro rx acepta una expresión regular.
import re
compileall.compile_dir(
"proyecto",
rx=re.compile(r"/(tests|vendor)/"),
quiet=1,
)
Prueba la expresión en Windows y Unix porque los separadores varían.
Salida quiet
quiet controla mensajes normales. La automatización puede reducir ruido conservando errores.
El silencio no sustituye observabilidad. Registra duración, cantidad de archivos, versión y resultado.
Workers paralelos
workers compila varios archivos concurrentemente.
compileall.compile_dir(
"src",
workers=4,
quiet=1,
)
Elige un límite acorde a CPU, filesystem y runner. Más workers pueden empeorar el rendimiento en storage lento.
Cantidad automática de workers
Versiones compatibles pueden aceptar valores especiales que calculan workers según el sistema. Verifica la documentación de tu intérprete.
Un límite explícito suele ser más predecible.
Niveles de optimización
optimize selecciona compilación normal, -O o -OO. APIs recientes pueden aceptar varios niveles.
compileall.compile_dir(
"src",
optimize=[0, 1, 2],
quiet=1,
)
Esto puede crear varios caches por fuente.
Qué cambia -O
El modo optimizado elimina asserts y hace falso __debug__. -OO también puede eliminar docstrings.
Nunca uses assert para validar entrada, autorización o invariantes críticas de producción.
hardlink_dupes
Cuando varios niveles generan datos idénticos, hardlink_dupes puede ahorrar espacio mediante hard links.
Prueba en containers, volúmenes, package builders y backups, porque soporte y semántica varían.
Modos de invalidación
Un .pyc utiliza una política basada en timestamps o hashes.
Los builds reproducibles suelen preferir hash para no depender de mtimes variables. Usa py_compile.PycInvalidationMode desde la API.
Timestamp frente a hash
Timestamp es rápido y común, pero depende de tamaño y modificación. Hash incorpora la identidad del contenido y encaja mejor en artefactos herméticos.
Elige una política consistente con instalación e importación.
SOURCE_DATE_EPOCH
Los sistemas de build reproducible pueden definir SOURCE_DATE_EPOCH, afectando defaults determinísticos.
No es suficiente por sí solo: controla orden, paths embebidos, permisos y versión del intérprete.
Filenames embebidos
Los code objects conservan filenames mostrados en tracebacks. Un path absoluto del runner puede filtrar infraestructura y no existir en destino.
Transforma paths para que correspondan al layout instalado.
stripdir
stripdir elimina un prefijo del filename guardado.
compileall.compile_dir(
"/build/work/src",
stripdir="/build/work",
quiet=1,
)
Comprueba que el prefijo coincida e inspecciona los tracebacks.
prependdir
prependdir agrega un prefijo después.
compileall.compile_dir(
"/build/work/src",
stripdir="/build/work/src",
prependdir="/opt/app",
quiet=1,
)
Así los filenames compilados apuntan al layout final.
Parámetros históricos
Existen opciones antiguas de directorio para compatibilidad. Prefiere strip y prepend actuales y documenta la versión mínima.
No combines opciones incompatibles sin comprobar la firma activa.
Builds en containers
Compila después de copiar el código, usando el mismo Python que ejecutará la aplicación.
RUN python -m compileall -q /opt/appCompilar en una versión y ejecutar otra produce caches ignorados o incompatibles.
No copies caches locales
Los caches de desarrollo pueden usar otra versión, optimización, paths o invalidación.
Ignora __pycache__ en Git y genera bytecode en el build final.
Despliegues read-only
La precompilación ayuda cuando el directorio será read-only y el usuario de runtime no puede crear caches.
Confirma que todos los fuentes necesarios fueron compilados y que la versión coincide.
Permisos
El usuario de build necesita leer fuente y crear directorios. Ajusta ownership para el usuario de ejecución.
No ejecutes la aplicación como root solo para crear bytecode.
Paquetes instalados
Los instaladores pueden compilar automáticamente. Evita duplicar trabajo sin motivo.
Usa compileall explícitamente para validación, optimización, reproducibilidad o imágenes read-only.
Namespace packages
Los namespace packages pueden abarcar varias raíces. Compila cada directorio instalado.
La compilación no valida la composición del namespace en runtime.
Errores de lectura
Archivos sin permiso, paths rotos y encoding inválido pueden fallar. Conserva el diagnóstico y falla el build si el código requerido no compila.
No ignores un resultado falso porque la mayoría de caches existan.
Fuente generada
Genera el código antes de compileall. Si un paso posterior cambia fuentes, el cache queda antiguo o se recompila al importar.
Ordena el pipeline: generación, formato, validación, compilación y packaging.
Entrada no confiable
No compiles árboles subidos por usuarios en el proceso principal. El parser consume recursos y links o mounts pueden escapar de límites.
Usa worker aislado, directorio temporal y límites estrictos.
El bytecode no es secreto
Distribuir solo .pyc dificulta lectura casual, pero herramientas inspeccionan constantes, nombres e instrucciones.
No guardes secretos en código ni prometas protección intelectual fuerte.
Compatibilidad
El formato cambia entre versiones. Un .pyc contiene un magic number y normalmente se rechaza en intérpretes incompatibles.
Compila con la misma implementación y major/minor del runtime.
Inspecciona con dis
dis muestra instrucciones y permite comparar optimizaciones.
Consulta dis en Python.
Valida imports
Después de compilar, ejecuta smoke tests de importación en entorno limpio.
python -c "import mi_paquete"Ejemplo de CI
python -m compileall -q -f src
pytestUsa la versión declarada por el proyecto.
Limpieza
Elimina caches antiguos borrando directorios __pycache__ dentro de una raíz validada.
No hagas borrado recursivo desde paths no comprobados ni sigas enlaces externos.
Prueba el build
Prueba árbol vacío, SyntaxError, archivo sin permiso, varios niveles, filenames transformados, ejecución read-only e imports en destino.
Compara hashes si la reproducibilidad es requisito.
Errores comunes
Los fallos frecuentes son tratar bytecode como ejecutable, compilar con otra versión, ignorar retorno falso, copiar caches locales, depender de asserts, embutir paths del runner, compilar antes de finalizar fuentes y creer que .pyc protege el código.
Conclusión
compileall prepara árboles Python para importación, valida sintaxis y controla optimización, concurrencia, filenames e invalidación. Ejecútalo en el build con el mismo intérprete de producción y trata cada fallo como problema del artefacto.
Genera caches en entorno limpio, conserva tracebacks útiles y valida imports después. Consulta la documentación oficial de compileall y sysconfig en Python para detalles del build.







