expat en Python: parser XML de bajo nivel

Publicado el: 23/08/2026
Tempo de leitura: 5 minutos
A person reads 'Python for Unix and Linux System Administration' indoors.

El módulo xml.parsers.expat expone directamente el parser XML Expat en Python. Es rápido, orientado a eventos y no valida contra schemas. En lugar de construir un árbol, la aplicación registra callbacks para inicios de elementos, texto, comentarios, namespaces y declaraciones.

Este nivel de control resulta útil en protocolos, conversores, analizadores e integraciones que necesitan rendimiento o callbacks especializados. Para la mayoría de aplicaciones, ElementTree en Python o xml.sax en Python ofrece una interfaz más sencilla.

Crear un parser

Usa ParserCreate(). Un encoding opcional sobrescribe la declaración del documento. Expat admite de forma nativa un conjunto limitado que incluye UTF-8, UTF-16, ISO-8859-1 y ASCII.

from xml.parsers import expat

parser = expat.ParserCreate()

Una instancia solo puede procesar un documento. Crea un parser nuevo para cada archivo.

Registrar handlers

Los handlers se asignan directamente a atributos del parser.

from xml.parsers import expat

def inicio(nombre, atributos):
    print("inicio", nombre, atributos)

def fin(nombre):
    print("fin", nombre)

def texto(datos):
    if datos.strip():
        print("texto", repr(datos))

parser = expat.ParserCreate()
parser.StartElementHandler = inicio
parser.EndElementHandler = fin
parser.CharacterDataHandler = texto
parser.Parse("<raiz><item id='1'>Hola</item></raiz>", True)

El último argumento de Parse() debe ser verdadero en la llamada final. Indica que no llegarán más datos y permite detectar tokens incompletos.

Parsing incremental

El documento puede enviarse en bloques. Esto ayuda con archivos grandes y streams, pero el volumen total sigue necesitando un límite explícito.

MAX_BYTES = 20 * 1024 * 1024
recibidos = 0

with open("datos.xml", "rb") as archivo:
    while bloque := archivo.read(64 * 1024):
        recibidos += len(bloque)
        if recibidos > MAX_BYTES:
            raise ValueError("XML demasiado grande")
        parser.Parse(bloque, False)

parser.Parse(b"", True)

Limita también eventos, profundidad, texto acumulado y tiempo. Leer por bloques evita una sola asignación enorme, pero no impide ataques de expansión.

El texto puede fragmentarse

Expat puede llamar CharacterDataHandler varias veces para un único texto lógico, especialmente en saltos de línea o límites de bloques. Acumula fragmentos hasta cerrar el elemento.

partes = []
parser.buffer_text = True

def texto(datos):
    partes.append(datos)

buffer_text=True reduce callbacks, pero no garantiza uno por elemento. buffer_size controla el buffer.

Namespaces

Pasa un separador de un carácter a ParserCreate() para activar namespaces.

parser = expat.ParserCreate(namespace_separator=" ")

def inicio(nombre, atributos):
    namespace, _, local = nombre.partition(" ")
    print(namespace, local)

Los nombres se expanden como URI, separador y nombre local. No dependas del prefijo original.

Posiciones y errores

Durante callbacks, CurrentLineNumber, CurrentColumnNumber y CurrentByteIndex informan la posición. Después de un error, usa los atributos de error y ErrorCode.

from xml.parsers import expat

try:
    parser.Parse("<raiz><item></raiz>", True)
except expat.ExpatError as error:
    mensaje = expat.ErrorString(error.code)
    print(mensaje, error.lineno, error.offset)

No registres el documento completo en producción. Guarda origen, posición y un contexto pequeño solo cuando sea seguro.

Atributos ordenados y especificados

Por defecto, los atributos llegan como diccionario. Con ordered_attributes=True, llegan como lista alternando nombre y valor según el orden de la fuente. La orden de atributos no debe tener significado semántico.

specified_attributes=True informa solo atributos presentes en el documento y omite defaults derivados de declaraciones. Requiere conocimiento cuidadoso de DTD.

Entidades externas

ExternalEntityRefHandler puede cargar recursos externos, pero expone archivos locales y red. Para XML controlado por usuarios, no implementes un handler que abra el systemId.

El artículo de xmlreader en Python muestra cómo bloquear fuentes externas en SAX. La regla es la misma: el documento no debe elegir recursos arbitrarios.

Entidades de parámetro y DTD

SetParamEntityParsing() controla entidades de parámetro y UseForeignDTD() permite solicitar una DTD alternativa. Estas funciones amplían la superficie de ataque y deberían permanecer desactivadas para documentos no confiables.

Reparse deferral

Expat 2.6 introdujo reparse deferral para evitar trabajo cuadrático cuando tokens enormes llegan en fragmentos. SetReparseDeferralEnabled(False) desactiva esa protección y puede reintroducir denegación de servicio.

if hasattr(parser, "GetReparseDeferralEnabled"):
    assert parser.GetReparseDeferralEnabled()

Mantén la protección activa. Un handler puede no ejecutarse inmediatamente después de cada bloque; es parte del mecanismo.

Protección contra billion laughs

Python 3.14.6 puede exponer métodos para ajustar la amplificación de entidades.

if hasattr(parser, "SetBillionLaughsAttackProtectionActivationThreshold"):
    parser.SetBillionLaughsAttackProtectionActivationThreshold(8 * 1024 * 1024)

if hasattr(parser, "SetBillionLaughsAttackProtectionMaximumAmplification"):
    parser.SetBillionLaughsAttackProtectionMaximumAmplification(100.0)

Los defaults dependen de la biblioteca Expat vinculada. Valores demasiado bajos pueden rechazar documentos legítimos. Prueba payloads reales y no aumentes límites sin entender la amenaza.

Protección de amplificación de memoria

Versiones recientes también exponen controles de asignación.

if hasattr(parser, "SetAllocTrackerActivationThreshold"):
    parser.SetAllocTrackerActivationThreshold(64 * 1024 * 1024)

if hasattr(parser, "SetAllocTrackerMaximumAmplification"):
    parser.SetAllocTrackerMaximumAmplification(100.0)

Estos controles complementan, pero no reemplazan, límites de bytes, tiempo, profundidad y eventos.

Comentarios, CDATA e instrucciones

Existen handlers para comentarios, límites CDATA, instrucciones, declaración XML, DTD y namespaces. CharacterDataHandler recibe texto normal y contenido CDATA; los handlers de CDATA permiten distinguir los límites sintácticos.

GetInputContext()

Durante un callback, GetInputContext() puede devolver datos cercanos al evento. Úsalo solo para diagnóstico controlado porque puede revelar información sensible.

ParseFile()

ParseFile() acepta un objeto con read(nbytes). Es cómodo, pero ofrece menos control sobre conteo de bytes y cancelación que un bucle explícito con Parse().

Errores de encoding

Encodings no soportados y bytes inválidos generan ExpatError. No sobrescribas el encoding solo para ocultar el error. Corrige la fuente o realiza una conversión deliberada.

Pruebas recomendadas

Prueba entrada completa y fragmentada, texto dividido, namespaces, encodings, documentos truncados, tags incompatibles, atributos duplicados, tokens enormes, entidades internas, DTD, entidades externas bloqueadas, profundidad y controles de amplificación.

Errores comunes

Los fallos frecuentes son reutilizar un parser, olvidar la llamada final, asumir un único callback de texto, desactivar reparse deferral, cargar entidades externas sin política, confiar solo en protecciones internas y omitir límites de aplicación.

Elegir otra API

Usa Expat directamente para callbacks de bajo nivel, rendimiento y control fino. Usa ElementTree para árboles, SAX para una interfaz estandarizada y pulldom en Python para fragmentos DOM selectivos.

Conclusión

xml.parsers.expat es rápido y detallado, pero la aplicación debe gestionar estado, texto fragmentado, límites y seguridad. Mantén reparse deferral y las protecciones de amplificación activas, evita entidades externas y crea un parser por documento.

Consulta la documentación oficial de Expat en Python y el proyecto oficial Expat.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026