Copiar grandes bloques binarios puede convertirse en un coste invisible en aplicaciones Python. Paquetes de red, imágenes, archivos mapeados y arreglos numéricos suelen atravesar varias capas, y cada conversión a un nuevo objeto bytes puede reservar memoria y duplicar datos. memoryview ofrece otra opción: expone una vista sobre cualquier objeto compatible con el protocolo de buffer, permitiendo inspeccionar o modificar regiones sin copiar todo el bloque.
En esta guía aprenderás a crear vistas, cortar buffers sin copias, trabajar con formatos y dimensiones, modificar datos mutables, combinar memoryview con array, mmap, struct y sockets, y evitar problemas de ciclo de vida.
Qué es el protocolo de buffer
El protocolo de buffer permite que un objeto exponga su memoria interna a otros componentes de Python. Tipos como bytes, bytearray y array.array lo implementan, además de muchos objetos de bibliotecas científicas y extensiones nativas.
Un slice normal de bytes crea un objeto nuevo. En cambio, un slice de memoryview normalmente crea otra vista que apunta al mismo almacenamiento.
datos = b"abcdefghijklmnopqrstuvwxyz"
vista = memoryview(datos)
parte = vista[5:10]
print(bytes(parte)) # b'fghij'
print(parte.obj is datos) # TrueEsto resulta útil cuando un parser o pipeline debe examinar distintas zonas de un buffer grande sin generar objetos temporales.
Vistas de solo lectura y escritura
La mutabilidad depende del objeto original. Una vista sobre bytes es de solo lectura. Una vista sobre bytearray puede modificar el almacenamiento original.
buffer = bytearray(b"ABCDE")
vista = memoryview(buffer)
vista[1:4] = b"xyz"
print(buffer) # bytearray(b'AxyzE')Los datos asignados deben tener una estructura compatible. Una vista no es una lista redimensionable; representa una región existente con formato y tamaño definidos.
Slices sin copia
Los slices sucesivos siguen compartiendo el mismo objeto base. Así, un parser puede devolver campos como vistas en lugar de copiar cada segmento.
def separar_paquete(paquete: bytes):
vista = memoryview(paquete)
version = vista[0:1]
longitud = vista[1:5]
payload = vista[5:]
return version, longitud, payloadMientras existan vistas activas, los objetos redimensionables no pueden cambiar de tamaño.
datos = bytearray(b"1234")
vista = memoryview(datos)
# datos.extend(b"5") # BufferError
vista.release()
datos.extend(b"5")Formato, itemsize y bytes totales
Una vista puede describir elementos mayores que un byte. Sobre array('I'), por ejemplo, expone enteros sin signo en vez de bytes aislados.
from array import array
numeros = array('I', [10, 20, 30, 40])
vista = memoryview(numeros)
print(vista.format)
print(vista.itemsize)
print(vista.nbytes)
print(vista.tolist())len(vista) suele contar elementos en la primera dimensión, mientras nbytes informa el tamaño total en bytes.
Reinterpretar memoria con cast
cast() cambia la forma de interpretar los mismos bytes. No convierte numéricamente los valores ni crea una copia transformada.
datos = bytearray(range(16))
plana = memoryview(datos)
matriz = plana.cast('B', shape=[4, 4])
print(matriz[2, 3])
matriz[0, 0] = 255
print(datos[0])El número total de bytes debe seguir siendo compatible. Para layouts binarios estructurados, combina vistas con struct en Python, que controla formatos, offsets y orden de bytes.
Archivos mapeados en memoria
Un archivo mapeado puede exponer su contenido como buffer. Las vistas representan regiones específicas sin leer todo el archivo en objetos separados.
import mmap
with open("datos.bin", "r+b") as archivo:
with mmap.mmap(archivo.fileno(), 0) as mapa:
vista = memoryview(mapa)
cabecera = vista[:64]
print(bytes(cabecera[:8]))
cabecera.release()
vista.release()Libera todas las vistas antes de cerrar el mapeo. De lo contrario, Python puede lanzar BufferError. Consulta también la guía de mmap en Python.
Recibir datos de sockets en buffers reutilizables
APIs como recv_into() pueden escribir directamente en un buffer existente.
buffer = bytearray(65536)
vista = memoryview(buffer)
# recibidos = sock.recv_into(vista)
# paquete = vista[:recibidos]Los buffers reutilizables reducen la presión de asignaciones en servidores de alto rendimiento. El diseño debe impedir que una región se reutilice mientras otra tarea todavía la está leyendo.
memoryview frente a bytes
Usa bytes cuando necesites un valor independiente, inmutable y fácil de almacenar o usar como clave. Usa memoryview cuando quieras slices sin copia, modificaciones in-place o interoperabilidad con APIs que aceptan buffers.
bytes(vista) crea una copia. Convierte solo en el límite donde la independencia sea realmente necesaria.
Reglas de ciclo de vida
- No redimensiones un
bytearraymientras existan vistas activas. - Libera vistas antes de cerrar un
mmapu otro recurso nativo. - No conserves una vista indefinidamente si el buffer proviene de un pool reutilizable.
- Documenta si una API devuelve datos propios o memoria prestada.
- Usa
with memoryview(obj) as vistacuando la liberación determinista mejore la claridad.
Parser binario sin copias intermedias
import struct
CABECERA = struct.Struct("!BI")
def analizar_paquete(datos: bytes):
vista = memoryview(datos)
if vista.nbytes < CABECERA.size:
raise ValueError("paquete incompleto")
tipo, longitud = CABECERA.unpack_from(vista, 0)
inicio = CABECERA.size
fin = inicio + longitud
if fin > vista.nbytes:
raise ValueError("payload truncado")
payload = vista[inicio:fin]
return tipo, payloadEl payload comparte memoria con datos. El llamador puede procesarlo directamente y crear una copia únicamente si necesita persistirlo.
Cuándo merece la pena
Para scripts pequeños y valores diminutos, los slices normales suelen ser más simples. memoryview destaca en pipelines binarios, redes, multimedia, análisis de archivos grandes, bibliotecas científicas e integración con código nativo.
Mide antes de optimizar. Herramientas como tracemalloc ayudan a comprobar si las copias temporales afectan realmente a la memoria. El código zero-copy también introduce reglas de propiedad, así que el beneficio debe justificar la complejidad.
Conclusión
memoryview es la interfaz estándar de Python para acceder a buffers prestados. Permite slices sin copia, vistas editables, metadatos de formato, casts multidimensionales e integración con array, struct, mmap y sockets.
La documentación oficial de memoryview en Python detalla operaciones y formatos. Úsalo cuando copiar sea un coste medible, manteniendo explícitas la propiedad, la mutabilidad y la duración del buffer.







