Copiar dados grandes pode ser um custo invisível em programas Python. Arquivos binários, pacotes de rede, imagens e matrizes frequentemente passam por várias camadas da aplicação, e cada conversão para bytes ou novo fatiamento pode duplicar memória. O memoryview oferece uma alternativa: ele cria uma visão sobre um objeto que suporta o protocolo de buffer, permitindo ler ou modificar regiões sem copiar todo o conteúdo.
Neste guia, você aprenderá como criar memoryview, usar slices sem cópia, interpretar formatos, converter dimensões, trabalhar com dados mutáveis, integrar com array, bytearray, mmap e struct, além de evitar armadilhas de ciclo de vida e compatibilidade.
O que é o protocolo de buffer?
O protocolo de buffer permite que um objeto exponha sua memória interna para outras partes do Python sem precisar transformá-la em uma nova sequência. Tipos como bytes, bytearray, array.array e objetos retornados por bibliotecas científicas podem participar desse protocolo.
Quando você escreve dados[100:200] em um objeto bytes, o Python cria outro objeto com os bytes selecionados. Com memoryview(dados)[100:200], você recebe apenas uma nova visão que aponta para a mesma memória.
dados = b"abcdefghijklmnopqrstuvwxyz"
visao = memoryview(dados)
parte = visao[5:10]
print(bytes(parte)) # b'fghij'
print(parte.obj is dados) # TrueEsse comportamento é valioso quando o volume de dados é alto ou quando várias etapas precisam examinar o mesmo buffer.
bytes, bytearray e mutabilidade
Uma visão herda a mutabilidade do objeto original. bytes é imutável, portanto a visão é somente leitura. bytearray pode ser modificado por meio da visão.
buffer = bytearray(b"ABCDE")
visao = memoryview(buffer)
visao[1:4] = b"xyz"
print(buffer) # bytearray(b'AxyzE')O tamanho da substituição precisa ser compatível com o slice. O memoryview não funciona como uma lista capaz de crescer durante a atribuição; ele representa uma região de memória com formato e tamanho definidos.
Slices sem cópia
Fatiamentos sucessivos continuam compartilhando o mesmo armazenamento. Isso permite criar parsers que entregam campos como visões, evitando alocações intermediárias.
def separar_cabecalho(pacote: bytes):
view = memoryview(pacote)
versao = view[0:1]
tamanho = view[1:5]
payload = view[5:]
return versao, tamanho, payloadEnquanto as views existirem, o objeto base deve permanecer válido. Em objetos redimensionáveis, como bytearray, uma view ativa impede operações que alterem o tamanho do buffer.
dados = bytearray(b"1234")
view = memoryview(dados)
# dados.extend(b"5") # BufferError enquanto view estiver ativa
view.release()
dados.extend(b"5")Formatos e itemsize
Um memoryview conhece o formato dos elementos, o tamanho de cada item e, em alguns casos, as dimensões. Uma visão sobre array('I'), por exemplo, representa inteiros sem sinal em vez de bytes isolados.
from array import array
numeros = array('I', [10, 20, 30, 40])
view = memoryview(numeros)
print(view.format)
print(view.itemsize)
print(view.tolist())O atributo nbytes informa o número total de bytes, enquanto len(view) normalmente representa a quantidade de elementos na primeira dimensão.
cast: reinterpretando o buffer
O método cast() permite reinterpretar os mesmos bytes com outro formato compatível. Isso não converte os valores; apenas muda a forma de enxergar a memória.
dados = bytearray(range(16))
bytes_view = memoryview(dados)
matriz = bytes_view.cast('B', shape=[4, 4])
print(matriz[2, 3])
matriz[0, 0] = 255
print(dados[0])O número total de bytes precisa permanecer consistente. Para protocolos binários complexos, combine memoryview com o módulo struct no Python, que interpreta campos com endianness e layouts explícitos.
Leitura eficiente de arquivos e mmap
O memoryview funciona muito bem com arquivos mapeados em memória. Um mmap pode expor o arquivo como buffer, e views podem representar regiões específicas.
import mmap
with open("dados.bin", "r+b") as arquivo:
with mmap.mmap(arquivo.fileno(), 0) as mapa:
view = memoryview(mapa)
cabecalho = view[:64]
print(bytes(cabecalho[:8]))
cabecalho.release()
view.release()Libere as views antes de fechar o mapeamento. Caso contrário, o Python pode gerar BufferError. Veja também o guia de mmap no Python.
Integração com sockets
APIs como socket.recv_into() podem gravar diretamente em um buffer reutilizável, reduzindo a criação de objetos.
buffer = bytearray(65536)
view = memoryview(buffer)
# quantidade = sock.recv_into(view)
# pacote = view[:quantidade]Em servidores de alto volume, buffers reutilizáveis ajudam a controlar pressão sobre o coletor de lixo. No entanto, o código precisa garantir que uma região não seja reutilizada enquanto outra tarefa ainda depende dela.
Comparando bytes e memoryview
Use bytes quando precisar de um valor independente, imutável, fácil de armazenar ou usar como chave de dicionário. Use memoryview quando quiser processar regiões de um buffer sem cópia, modificar dados existentes ou passar memória entre APIs compatíveis.
Converter uma view para bytes cria uma cópia. Faça isso apenas no limite onde um objeto independente seja realmente necessário.
Cuidados com o ciclo de vida
- Não redimensione um
bytearrayenquanto houver views exportadas. - Libere views antes de fechar
mmapou outros recursos subjacentes. - Não guarde uma view por tempo indefinido se o buffer pertence a um pool reutilizável.
- Documente se uma função retorna dados independentes ou uma visão compartilhada.
- Use
with memoryview(obj) as viewquando quiser liberação determinística.
Exemplo: parser binário sem cópias intermediárias
import struct
HEADER = struct.Struct("!BI")
def analisar_pacote(dados: bytes):
view = memoryview(dados)
if view.nbytes < HEADER.size:
raise ValueError("pacote incompleto")
tipo, tamanho = HEADER.unpack_from(view, 0)
inicio = HEADER.size
fim = inicio + tamanho
if fim > view.nbytes:
raise ValueError("payload truncado")
payload = view[inicio:fim]
return tipo, payloadO payload retornado compartilha a memória com dados. O chamador pode processá-lo sem alocação adicional e convertê-lo somente quando precisar persistir o conteúdo.
Quando o ganho importa?
Para pequenos textos e scripts ocasionais, a simplicidade de bytes costuma ser suficiente. O benefício do memoryview aparece em pipelines binários, processamento de arquivos grandes, redes, multimídia, computação científica e bibliotecas que trocam buffers com código nativo.
Meça antes de otimizar. O custo de cópia pode ser observado com ferramentas de profiling e com o módulo tracemalloc. Uma arquitetura zero-copy mal documentada pode introduzir bugs mais difíceis do que a economia obtida.
Conclusão
O memoryview é a interface nativa do Python para trabalhar com buffers compartilhados. Ele permite criar slices sem cópia, editar regiões mutáveis, reinterpretar formatos e integrar dados binários com módulos como array, struct, mmap e sockets.
A documentação oficial de memoryview no Python 3 detalha formatos, casts e operações suportadas. Use a ferramenta quando o custo de cópia for relevante, mantendo explícitos a mutabilidade, o proprietário do buffer e o ciclo de vida das views.







