O protocolo de buffer do Python permite que bibliotecas trabalhem com dados binários sem copiar todo o conteúdo para um novo objeto. Tipos como bytes, bytearray, memoryview e vários arrays de bibliotecas científicas expõem memória de forma eficiente. A classe abstrata collections.abc.Buffer oferece uma maneira padronizada de representar, em anotações de tipo, qualquer objeto que suporte esse protocolo.
Neste guia, você vai entender o que é Buffer, como utilizá-lo em funções, quando criar uma memoryview, como evitar cópias desnecessárias e quais cuidados tomar com mutabilidade, tempo de vida e validação de dados.
O que é o protocolo de buffer
O protocolo de buffer é uma interface de baixo nível usada para compartilhar uma região de memória entre objetos Python e extensões nativas. Em vez de converter um bloco binário para outro formato, uma função pode acessar os mesmos bytes diretamente. Isso é importante em processamento de imagens, redes, compressão, criptografia, áudio, bancos de dados e computação científica.
O usuário normalmente não chama o protocolo diretamente. A forma mais comum de consumi-lo é criar uma memoryview.
dados = bytearray(b"Python")
visao = memoryview(dados)
print(visao[0])
visao[0] = ord("J")
print(dados)
A visão aponta para o mesmo armazenamento do bytearray. A alteração feita pela memoryview aparece no objeto original.
Por que collections.abc.Buffer existe
Antes de existir uma ABC dedicada, bibliotecas precisavam usar tipos muito específicos, protocolos próprios ou anotações amplas como object. Isso dificultava comunicar que uma função aceita qualquer objeto exportador de buffer. collections.abc.Buffer resolve esse problema no nível da tipagem e da documentação da API.
from collections.abc import Buffer
def tamanho_binario(dados: Buffer) -> int:
return memoryview(dados).nbytes
print(tamanho_binario(b"abc"))
print(tamanho_binario(bytearray(b"abc")))
A função não exige especificamente bytes. Ela aceita qualquer valor compatível com o protocolo de buffer.
Buffer não é o conteúdo em si
Buffer descreve uma capacidade. Ele não substitui bytes, não cria armazenamento e não garante que os dados sejam mutáveis. Para acessar propriedades concretas, crie uma memoryview. A visão informa tamanho, formato, número de dimensões, itemsize e se a região é somente leitura.
from collections.abc import Buffer
def descrever(dados: Buffer) -> dict[str, object]:
view = memoryview(dados)
return {
"bytes": view.nbytes,
"formato": view.format,
"dimensoes": view.ndim,
"somente_leitura": view.readonly,
}
Leitura sem cópia
Uma vantagem central é poder fatiar uma visão sem duplicar o bloco completo. Isso é útil quando um pacote possui cabeçalho e corpo.
from collections.abc import Buffer
def separar_pacote(pacote: Buffer) -> tuple[memoryview, memoryview]:
view = memoryview(pacote)
if view.nbytes < 4:
raise ValueError("pacote incompleto")
return view[:4], view[4:]
As duas visões compartilham a mesma memória do objeto recebido. Converta para bytes apenas quando realmente precisar de uma cópia independente.
Mutabilidade e segurança
Nem todo buffer pode ser alterado. Uma visão criada sobre bytes é somente leitura, enquanto uma visão sobre bytearray normalmente permite escrita. Verifique readonly antes de modificar.
from collections.abc import Buffer
def zerar_primeiro_byte(dados: Buffer) -> None:
view = memoryview(dados)
if view.readonly:
raise TypeError("o buffer é somente leitura")
if view.nbytes:
view[0] = 0
Uma anotação Buffer sozinha não promete mutabilidade. Se sua API exige escrita, documente isso claramente e valide em tempo de execução.
Formatos e casts
Uma memoryview pode representar elementos maiores que um byte. O atributo format segue convenções do módulo struct. Em alguns casos, é possível reinterpretar a memória com cast, desde que o tamanho seja compatível.
numeros = bytearray([1, 0, 2, 0])
view = memoryview(numeros)
inteiros = view.cast("H")
print(list(inteiros))
O resultado depende da representação e da ordem de bytes da plataforma. Em formatos de arquivo e protocolos de rede, use struct quando precisar controlar explicitamente endianness e alinhamento.
Tempo de vida da memória
A visão mantém referência ao exportador, mas alguns objetos não podem ser redimensionados enquanto uma memoryview ativa aponta para eles. Um bytearray, por exemplo, pode gerar BufferError se você tentar alterar seu tamanho antes de liberar a visão.
dados = bytearray(b"abc")
view = memoryview(dados)
try:
dados.extend(b"d")
finally:
view.release()
Use a memoryview como gerenciador de contexto quando o escopo puder ser curto.
with memoryview(bytearray(b"abc")) as view:
print(view.nbytes)
APIs que aceitam Buffer
Uma boa função que aceita Buffer deve deixar claro se lê, modifica, retém ou copia os dados. Considere uma função que calcula uma soma simples.
from collections.abc import Buffer
def checksum(dados: Buffer) -> int:
view = memoryview(dados).cast("B")
return sum(view) % 256
O cast para B cria uma visão byte a byte. A função não modifica nem retém a memória após retornar.
Quando converter para bytes
Zero-copy nem sempre é a melhor escolha. Converta para bytes quando precisar armazenar o conteúdo por muito tempo, atravessar uma fronteira assíncrona sem controlar a mutabilidade, usar o valor como chave de dicionário ou garantir uma fotografia imutável do estado.
from collections.abc import Buffer
def snapshot(dados: Buffer) -> bytes:
return bytes(memoryview(dados))
Essa conversão cria uma cópia previsível e independente.
Validação de tamanho
Não confie apenas no tipo. Dados binários podem estar truncados ou ter tamanho incompatível. Valide limites antes de acessar posições.
from collections.abc import Buffer
def ler_codigo(dados: Buffer) -> int:
view = memoryview(dados).cast("B")
if len(view) < 2:
raise ValueError("são necessários pelo menos dois bytes")
return (view[0] << 8) | view[1]
Integração com outras ferramentas
O tema se conecta ao guia sobre collections no Python, ao conteúdo de arquivos grandes, ao artigo sobre módulo os e ao tutorial de arquivos CSV. Para detalhes técnicos, consulte a documentação de collections.abc e a documentação do protocolo de buffer.
Compatibilidade
Confirme a versão mínima do Python adotada pelo projeto antes de importar Buffer. Em bibliotecas que suportam versões antigas, você pode manter uma anotação alternativa ou usar dependências de tipagem compatíveis. Evite capturar ImportError silenciosamente sem testes, pois isso pode esconder uma configuração incorreta.
Boas práticas
Use Buffer para expressar aceitação ampla de dados binários, mas converta imediatamente para memoryview dentro da função. Valide tamanho, formato e mutabilidade. Não retenha uma visão além do necessário. Libere-a quando o exportador precisar ser redimensionado. Faça cópia com bytes quando estabilidade for mais importante que evitar alocação.
Conclusão
collections.abc.Buffer melhora a clareza de APIs binárias ao representar qualquer objeto compatível com o protocolo de buffer. Com memoryview, é possível acessar e fatiar memória com poucas cópias, mantendo desempenho em redes, arquivos e processamento científico. O recurso exige disciplina: a API deve documentar leitura ou escrita, validar tamanhos, respeitar o tempo de vida da memória e copiar quando necessário. Usado corretamente, ele torna funções mais genéricas, eficientes e fáceis de verificar por ferramentas de tipagem.







