py_compile en Python: genera archivos pyc

Publicado el: 04/08/2026
Tempo de leitura: 6 minutos
Monitor con código binario que representa generación de archivos pyc con py_compile en Python

Cuando se importa un módulo Python, el intérprete puede guardar su bytecode en un archivo .pyc dentro de __pycache__. El módulo py_compile en Python permite generar este caché anticipadamente, validar archivos durante builds y controlar cómo el intérprete decide si el bytecode todavía corresponde al código fuente.

Esta guía explica py_compile.compile(), PyCompileError, niveles de optimización e invalidación por timestamp o hash. Complementa nuestros artículos sobre bytecode con dis, compileall, tracebacks, tokenize y compilación interactiva.

Qué es un archivo pyc

Un archivo .pyc contiene bytecode serializado y un encabezado con información para validar el caché. Puede evitar recompilar código sin cambios en determinados arranques, pero no convierte Python en un binario nativo independiente.

El bytecode sigue siendo específico de la implementación y versión. Un archivo creado para una etiqueta de caché de CPython no debe considerarse compatible con otra.

Compilar un archivo fuente

La función principal recibe la ruta del archivo.

import py_compile

ruta = py_compile.compile("mi_modulo.py")
print(ruta)

Por defecto, el resultado sigue las convenciones PEP 3147 y PEP 488, normalmente dentro de __pycache__ con la etiqueta del intérprete.

Validar sintaxis durante el build

La compilación anticipada permite detectar SyntaxError antes del despliegue.

from pathlib import Path
import py_compile

for archivo in Path("src").rglob("*.py"):
    py_compile.compile(
        str(archivo),
        doraise=True,
    )

Para árboles enteros, compileall es más cómodo. py_compile resulta apropiado cuando la aplicación ya dispone de una lista exacta.

doraise y PyCompileError

Por defecto, un error se escribe en stderr y la función devuelve None. Con doraise=True, se lanza PyCompileError.

try:
    py_compile.compile(
        "roto.py",
        doraise=True,
    )
except py_compile.PyCompileError as error:
    print("La compilación falló:", error)

Las aplicaciones y pipelines deberían preferir la excepción para controlar el estado y producir reportes estructurados.

Elegir el destino con cfile

cfile selecciona explícitamente el archivo de salida.

py_compile.compile(
    "modulo.py",
    cfile="build/modulo.pyc",
    doraise=True,
)

Crea antes el directorio. Las rutas personalizadas pueden servir para empaquetado, mientras las convenciones estándar facilitan la importación y convivencia entre versiones.

Si el destino calculado es un enlace simbólico o archivo no regular, la función lanza FileExistsError.

La documentación oficial de py_compile explica que la escritura sigue la semántica de importlib, mediante archivo temporal y renombrado para reducir resultados parciales en concurrencia. La comprobación evita reemplazar objetos especiales silenciosamente.

Nombre fuente en tracebacks

dfile define el nombre almacenado para tracebacks y mensajes.

py_compile.compile(
    "src/paquete/modulo.py",
    dfile="/app/paquete/modulo.py",
    doraise=True,
)

Es útil cuando la ruta de build difiere de la ruta de despliegue. Usa una ubicación significativa sin exponer directorios privados.

Nivel de optimización

optimize se pasa a compile(). El valor predeterminado -1 selecciona el nivel del intérprete actual.

py_compile.compile(
    "modulo.py",
    optimize=1,
    doraise=True,
)

El nivel 1 elimina asserts y cambia __debug__. El nivel 2 también elimina muchas docstrings. No uses optimización para ocultar código ni supongas una mejora relevante sin medir.

Cachés separados por optimización

Con el destino estándar, la etiqueta incluye la variante de optimización. Varios niveles pueden coexistir dentro de __pycache__.

Si eliges cfile manualmente, evita sobrescribir variantes necesarias. Registra versión, implementación y optimización en el build.

Modos de invalidación

PycInvalidationMode determina cómo Python comprueba que un .pyc está actualizado.

  • TIMESTAMP: compara timestamp y tamaño del fuente;
  • CHECKED_HASH: almacena un hash y lo verifica al importar;
  • UNCHECKED_HASH: almacena un hash, pero confía en un sistema externo.

El modo queda registrado en el encabezado.

Invalidación por timestamp

from py_compile import PycInvalidationMode

py_compile.compile(
    "modulo.py",
    doraise=True,
    invalidation_mode=PycInvalidationMode.TIMESTAMP,
)

Es rápido y suele ser el valor predeterminado cuando SOURCE_DATE_EPOCH no está definido. Sistemas de archivos con baja resolución temporal pueden producir casos límite.

Hash verificado

py_compile.compile(
    "modulo.py",
    doraise=True,
    invalidation_mode=PycInvalidationMode.CHECKED_HASH,
)

El intérprete vuelve a calcular el hash del fuente al importar. Mejora el determinismo y evita depender solo de timestamps, con coste adicional de lectura y hashing.

Hash no verificado

py_compile.compile(
    "modulo.py",
    doraise=True,
    invalidation_mode=PycInvalidationMode.UNCHECKED_HASH,
)

Python asume que el caché es correcto. Úsalo solo cuando un gestor de paquetes o build system actualiza rigurosamente los archivos.

SOURCE_DATE_EPOCH

Cuando SOURCE_DATE_EPOCH está definida, el modo predeterminado pasa a CHECKED_HASH. Esto ayuda a builds reproducibles que no deben depender de timestamps reales.

Desde Python 3.7.2, la variable determina el default, pero no sustituye un argumento explícito.

El parámetro quiet

quiet controla mensajes cuando doraise=False.

  • 0 o 1: diagnóstico normal;
  • 2: sin mensajes y doraise deja de tener efecto.

La automatización debería preferir doraise=True y manejar la excepción.

Interfaz de terminal

El módulo puede compilar archivos nombrados explícitamente.

python -m py_compile archivo1.py archivo2.py

No busca recursivamente en directorios. El código de salida es distinto de cero si algún archivo no se compila.

Leer nombres desde stdin

Cuando - es el único argumento, los nombres se leen de la entrada estándar.

find src -name '*.py' -print | python -m py_compile -

En Unix, los nombres pueden contener newlines. Un pipeline estricto puede preferir recorrer rutas con Path.

Modo quiet en la CLI

python -m py_compile -q archivo1.py archivo2.py

La opción reduce mensajes, pero el pipeline todavía debe comprobar el status.

Permisos e instalaciones compartidas

La compilación anticipada es útil cuando los usuarios pueden leer el paquete, pero no escribir en __pycache__.

El instalador con privilegios adecuados crea los cachés. Después, los permisos deberían permitir lectura sin otorgar escritura innecesaria.

Escrituras concurrentes

La estrategia temporal y el renombrado reduce archivos parciales cuando varios procesos compilan el mismo destino. No hace seguro dirigir fuentes distintas a un mismo cfile.

Usa directorios separados por intérprete y evita jobs concurrentes sobre un artefacto personalizado compartido.

Los pyc no protegen la lógica

El bytecode puede inspeccionarse y desmontarse. Distribuir solamente .pyc no proporciona protección fuerte de propiedad intelectual, firma ni cifrado.

El bytecode no confiable sigue siendo peligroso cuando se importa, igual que el fuente.

Eliminar cachés antiguos

La mayoría de proyectos no debería versionar __pycache__. Al cambiar de intérprete o crear un build limpio, elimina y regenera.

from pathlib import Path

for carpeta in Path(".").rglob("__pycache__"):
    for archivo in carpeta.iterdir():
        archivo.unlink()
    carpeta.rmdir()

Usa la limpieza con cuidado en entornos compartidos.

Helper controlado

from pathlib import Path
import py_compile


def compilar(archivo: Path) -> Path:
    salida = py_compile.compile(
        str(archivo),
        doraise=True,
        optimize=0,
        invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
    )
    return Path(salida)

El llamador puede registrar tamaño, digest y versión de Python.

Pruebas

Crea fuentes temporales válidas e inválidas y verifica destinos, excepciones y política de invalidación.

from tempfile import TemporaryDirectory

with TemporaryDirectory() as carpeta:
    fuente = Path(carpeta) / "ok.py"
    fuente.write_text("valor = 42\n", encoding="utf-8")
    pyc = compilar(fuente)
    assert pyc.exists()

Prueba también un destino symlink en plataformas compatibles.

Errores frecuentes

  • Suponer que pyc es portable entre versiones.
  • Ignorar None con doraise=False.
  • Usar una salida personalizada para varias fuentes.
  • Elegir hash no verificado sin un build confiable.
  • Distribuir pyc como protección del fuente.
  • Confundir compilación con pruebas funcionales.
  • No registrar el nivel de optimización.
  • Versionar cachés locales sin necesidad.

Buenas prácticas

  • Usa doraise=True en automatización.
  • Prefiere rutas estándar de __pycache__.
  • Selecciona la invalidación explícitamente para builds reproducibles.
  • Separa artefactos por intérprete y optimización.
  • Compila antes del despliegue para detectar sintaxis.
  • Ejecuta tests además de compilar.
  • No importes bytecode no confiable.
  • Limpia cachés al cambiar de entorno.

Conclusión

El módulo py_compile en Python compila fuentes en cachés .pyc, valida sintaxis y prepara instalaciones donde el runtime no puede escribir en el paquete. Controla destino, nombre de traceback, optimización e invalidación.

La elección entre timestamps y hashes depende del build. Con excepciones explícitas, rutas convencionales y artefactos aislados por versión, py_compile hace predecible la generación de bytecode sin confundir un caché con ejecutable portable, protección del código o prueba funcional.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Editor de código que representa corrección de tabs y espacios con tabnanny en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tabnanny en Python: corrige la indentación

    Aprende tabnanny en Python para detectar tabs y espacios ambiguos, revisar proyectos y evitar TabError e IndentationError.

    Ler mais

    Tempo de leitura: 7 minutos
    04/08/2026
    Código fuente y sintaxis que representan análisis léxico con tokenize en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tokenize en Python: analiza código fuente

    Aprende tokenize en Python para analizar tokens, comentarios, encoding, posiciones y reconstruir código fuente con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    04/08/2026
    Terminal de programación que representa compilación de entradas interactivas con codeop en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    codeop en Python: compila entradas interactivas

    Aprende codeop en Python para detectar entradas completas, compilar comandos de REPL y conservar __future__ con seguridad.

    Ler mais

    Tempo de leitura: 7 minutos
    04/08/2026
    Desarrollador analizando estructura de código y tablas de símbolos con symtable en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    symtable en Python: ámbitos y símbolos

    Aprende symtable en Python para analizar ámbitos, símbolos, globals, nonlocals, closures, imports, annotations y type parameters.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026
    Monitor con código binario que representa análisis de bytecode con dis en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    dis en Python: entiende el bytecode

    Aprende dis en Python para analizar bytecode, instrucciones, cachés adaptativas, posiciones, tracebacks y detalles internos de CPython.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026
    Pantalla de error que representa diagnóstico de crashes y deadlocks con faulthandler en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    faulthandler en Python: diagnostica bloqueos

    Aprende faulthandler en Python para diagnosticar crashes, deadlocks y timeouts mediante pilas de threads y código nativo.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026