El módulo mmap crea objetos de memoria mapeada que permiten acceder al contenido de un archivo como una secuencia direccionable de bytes. En lugar de copiar todo el archivo a un objeto Python, el sistema operativo relaciona regiones del archivo con el espacio de direcciones del proceso y carga páginas a medida que se utilizan. Esto puede simplificar acceso aleatorio, búsqueda, edición binaria, intercambio entre procesos y procesamiento de archivos muy grandes.
La memoria mapeada no vuelve rápido cualquier algoritmo. El resultado depende del patrón de acceso, page faults, cache del sistema, tamaño del archivo y necesidad de sincronización. También existen diferencias entre Windows y Unix. Usa mmap cuando el acceso por posición, la integración con el buffer protocol o el uso compartido de páginas ofrezcan una ventaja real.
Abre un archivo para mapearlo
El modo del archivo debe ser compatible con el tipo de acceso. Para lectura y escritura, usa un descriptor que permita ambas operaciones.
from pathlib import Path
import mmap
ruta = Path("datos.bin")
with ruta.open("r+b") as archivo:
with mmap.mmap(archivo.fileno(), 0) as mapa:
print(len(mapa))
print(mapa[:16])
Una longitud igual a cero mapea el tamaño actual del archivo en plataformas compatibles. Los archivos vacíos normalmente no pueden mapearse de esta forma.
Usa context managers
Un objeto mmap mantiene un recurso nativo. El bloque with garantiza el cierre incluso cuando el procesamiento falla.
Mantén también explícito el lifecycle del archivo original. Aunque un mapa puede seguir válido después de cerrar el descriptor en muchos sistemas, evita depender de detalles no documentados.
Mapeo de solo lectura
Si la aplicación únicamente consulta datos, abre el archivo en modo binario de lectura y solicita acceso de lectura.
with open("indice.bin", "rb") as archivo:
with mmap.mmap(
archivo.fileno(),
0,
access=mmap.ACCESS_READ,
) as mapa:
posicion = mapa.find(b"CLAVE=")
print(posicion)
Intentar modificar un mapa read-only genera error. La restricción reduce el impacto de bugs y comunica mejor la intención.
Índices y slices
El objeto se comporta de forma parecida a una secuencia mutable de bytes. Un índice devuelve un entero y un slice devuelve bytes.
primero = mapa[0]
header = mapa[0:32]
Los slices grandes todavía copian datos. Usa memoryview para integrar APIs compatibles sin una copia adicional y controla cuidadosamente su lifecycle.
Cursor interno
Además del acceso indexado, mmap ofrece métodos como read(), readline(), seek() y tell().
mapa.seek(100)
bloque = mapa.read(64)
print(mapa.tell())
El cursor es estado mutable compartido. Dos partes del programa pueden interferirse. Prefiere offsets explícitos o sincronización.
Busca patrones de bytes
find() y rfind() buscan secuencias sin crear primero otra copia completa del archivo.
inicio = 0
while True:
posicion = mapa.find(b"ERROR", inicio)
if posicion == -1:
break
print(posicion)
inicio = posicion + 5
El patrón es útil en logs y formatos binarios, pero el texto todavía exige encoding y límites correctos.
Texto y encoding
El mapa contiene bytes. Decodifica solo la región necesaria.
linea = mapa[inicio:fin].decode("utf-8", errors="strict")
Un slice puede cortar un carácter multibyte. Busca delimitadores en bytes o usa un decoder incremental al procesar chunks.
Modifica bytes en el lugar
Un mapa escribible puede sustituir bytes sin reescribir todo el archivo.
with open("registro.bin", "r+b") as archivo:
with mmap.mmap(archivo.fileno(), 0) as mapa:
mapa[8:12] = b"DONE"
mapa.flush()
La cantidad asignada debe coincidir con la región reemplazada. Un objeto mmap no crece como una lista.
Flush y durabilidad
flush() solicita que las páginas modificadas sean escritas. La durabilidad exacta depende del sistema, filesystem, cache del dispositivo y flags.
Para datos críticos, combina el diseño con archivo temporal, rename atómico, fsync() y un protocolo transaccional cuando corresponda. Un flush no convierte varias escrituras en una transacción.
ACCESS_COPY
ACCESS_COPY crea una vista privada copy-on-write. El proceso observa sus cambios, pero el archivo original no se modifica.
with mmap.mmap(
archivo.fileno(),
0,
access=mmap.ACCESS_COPY,
) as mapa:
mapa[0:4] = b"TEST"
Es útil para transformaciones temporales. Las páginas modificadas pueden aumentar el uso de memoria.
Mapea solo una región
Los archivos enormes pueden procesarse mediante ventanas pequeñas. El offset debe respetar la granularidad de asignación de la plataforma.
granularidad = mmap.ALLOCATIONGRANULARITY
offset = (inicio // granularidad) * granularidad
delta = inicio - offset
longitud = delta + tamano
with mmap.mmap(
archivo.fileno(),
longitud,
access=mmap.ACCESS_READ,
offset=offset,
) as mapa:
datos = mapa[delta:delta + tamano]
Los offsets desalineados son una causa frecuente de errores.
Archivos mayores que la RAM
El sistema puede mapear un archivo mayor que la memoria física porque carga páginas bajo demanda. Sin embargo, un recorrido aleatorio puede producir paging intenso y thrashing.
Prefiere acceso secuencial, mide memoria residente y page faults y evita mantener muchos mapas gigantes abiertos.
Redimensionamiento
resize() puede cambiar el tamaño en determinadas plataformas y modos. El soporte varía.
Para código portable, cierra el mapa, redimensiona el archivo y crea uno nuevo. Recalcula todos los offsets.
Crea un archivo con tamaño previo
El archivo debe contener suficientes bytes antes de escribir en regiones futuras.
tamano = 1024 * 1024
with open("bloque.bin", "w+b") as archivo:
archivo.truncate(tamano)
with mmap.mmap(archivo.fileno(), tamano) as mapa:
mapa[0:4] = b"DATA"
Los archivos sparse y la reserva física dependen del filesystem. truncate() no garantiza que todos los bloques estén asignados.
Mapeos anónimos
En plataformas compatibles, un mapa puede crearse sin archivo como buffer de trabajo o memoria compartida.
with mmap.mmap(-1, 4096) as mapa:
mapa[:5] = b"hello"
print(mapa[:5])
Las reglas de nombre y compartición difieren entre Windows y Unix. Evalúa también multiprocessing.shared_memory.
Compartir entre procesos
Procesos que mapean la misma región pueden observar cambios compartidos según el modo. El mapeo no sincroniza operaciones.
Usa locks, semáforos, marcadores de versión, checksums y un protocolo de publicación. Un lector no debe analizar una estructura mientras el escritor la modifica parcialmente.
Threads y estado compartido
Las lecturas indexadas independientes pueden ser sencillas, pero operaciones con seek() y read() requieren coordinación cuando se comparte el objeto.
Protege escrituras concurrentes o centraliza los cambios. La ausencia de excepción no garantiza consistencia.
Cambios externos del archivo
Si otro proceso trunca un archivo mapeado, un acceso posterior puede provocar errores graves, señales del sistema o referencias inválidas.
Define ownership y prohíbe redimensionar mientras existan lectores. Para actualizaciones completas, crea un archivo nuevo y sustitúyelo de forma atómica.
Buffer protocol y memoryview
Un memoryview permite que bibliotecas compatibles consuman bytes sin otra copia.
with mmap.mmap(archivo.fileno(), 0, access=mmap.ACCESS_READ) as mapa:
vista = memoryview(mapa)
try:
consumir_buffer(vista[100:200])
finally:
vista.release()
El mapa no puede cerrarse mientras existan views exportadas. Libéralas explícitamente.
Integración con struct
Los formatos binarios de layout fijo pueden decodificarse directamente con struct.unpack_from().
import struct
version, longitud = struct.unpack_from("!HI", mapa, 0)
Comprueba el tamaño mínimo y no confíes en longitudes leídas de archivos externos.
Rendimiento
Compara mmap con lectura por chunks, readinto() y APIs de alto nivel. Para recorridos secuenciales simples, la lectura buffered puede ser igual de rápida y más fácil.
Mide tiempo total, memoria, page faults y carga real. Un benchmark con el archivo ya en cache puede engañar.
Seguridad
Un archivo mapeado sigue siendo entrada no confiable. Valida magic bytes, firma, tamaños, contadores, offsets y límites antes de indexar posiciones calculadas.
No uses valores del archivo en aritmética de slices sin límites. Un archivo malicioso puede provocar consumo excesivo o acceso inválido.
Manejo de errores
Apertura, mapeo, flush e índices pueden lanzar OSError, ValueError, TypeError o errores de índice.
try:
with open(ruta, "rb") as archivo:
with mmap.mmap(archivo.fileno(), 0, access=mmap.ACCESS_READ) as mapa:
analizar(mapa)
except (OSError, ValueError) as error:
raise RuntimeError(f"no se pudo mapear {ruta}") from error
Incluye ruta y operación en el diagnóstico sin exponer datos sensibles.
Pruebas
Prueba archivo vacío, tamaño mínimo, truncamiento, permisos, offsets desalineados, cambios concurrentes, estructuras inválidas, límites, flush, Windows y Unix.
Usa archivos temporales reales. Los mocks no reproducen alineación ni semántica del sistema operativo.
Errores comunes
Los fallos frecuentes son mapear archivos vacíos, abrir con modo incompatible, olvidar alineación, suponer que los slices no copian, cerrar con memoryview activo, redimensionar externamente, tratar flush() como transacción y usar acceso aleatorio sin medir paging.
Conclusión
mmap convierte archivos y regiones compartidas en buffers direccionables. Es especialmente útil para búsqueda en archivos grandes, formatos binarios, acceso aleatorio e integración con APIs de buffer.
Mantén ownership explícito, valida límites, sincroniza escritores y compara con I/O buffered. Consulta la documentación oficial de mmap, multiprocessing en Python y contextlib en Python.







