memoryview.count permite contar quantas vezes um valor aparece em uma visão de memória sem converter todo o buffer para bytes ou list. Isso é útil em processamento binário, protocolos, arquivos mapeados, imagens, áudio, redes e qualquer fluxo no qual copiar dados grandes aumenta o consumo de memória e o tempo de execução.
Neste guia, você vai entender como o método funciona, quais tipos de valores ele aceita, como o formato do buffer altera o resultado, quando usar count em vez de uma conversão, como lidar com fatias e estruturas multidimensionais e quais cuidados adotar ao processar dados externos.
O que é memoryview
Um memoryview expõe o protocolo de buffer do Python. Em vez de criar uma nova sequência com os mesmos dados, ele oferece uma janela sobre um objeto existente, como bytes, bytearray, array.array ou memória compartilhada. Alterações feitas em uma visão gravável podem atingir o objeto original, enquanto uma visão somente leitura permite inspeção eficiente.
dados = bytearray(b"ABRACADABRA")
visao = memoryview(dados)
print(visao[0])
print(len(visao))
Quando o formato é de bytes sem sinal, cada posição representa um inteiro entre 0 e 255. Por isso, contar a letra A significa contar o valor numérico retornado por ord("A").
Contagem básica
dados = memoryview(b"ABRACADABRA")
quantidade = dados.count(ord("A"))
print(quantidade)
O método percorre a visão e retorna a quantidade de elementos iguais ao valor informado. Ele não devolve posições e não altera o buffer. Para localizar a primeira ocorrência, use uma operação apropriada de busca; para reunir todas as posições, faça uma iteração controlada.
Por que evitar conversões
Uma solução comum é chamar bytes(visao).count(...). Ela funciona, mas materializa uma cópia quando a visão cobre outro objeto ou uma fatia complexa. Em buffers pequenos isso pode ser irrelevante. Em arquivos grandes, pacotes de rede ou lotes científicos, a cópia pode dobrar temporariamente o uso de memória.
def contar_zero(buffer):
visao = memoryview(buffer)
return visao.count(0)
A função recebe qualquer objeto compatível com o protocolo de buffer e conta bytes nulos sem impor uma representação intermediária.
Contando delimitadores em dados binários
Imagine um protocolo no qual mensagens são separadas por quebra de linha. Antes de dividir o conteúdo, você pode estimar quantos registros existem.
pacote = memoryview(b"um\ndois\ntres\n")
linhas = pacote.count(ord("\n"))
print(linhas)
Essa contagem não valida o protocolo inteiro. Ela apenas fornece uma métrica rápida para pré-alocação, diagnóstico ou checagem inicial.
Fatias sem copiar
Fatias de memoryview normalmente continuam referenciando a mesma memória. Assim, você pode contar valores em uma região específica.
dados = bytearray(range(100))
visao = memoryview(dados)
regiao = visao[20:60]
print(regiao.count(42))
Isso é útil para cabeçalhos, payloads e blocos fixos. Mesmo assim, valide os limites antes de criar a fatia, principalmente quando offsets vêm de arquivos ou da rede.
O formato importa
memoryview não representa apenas bytes. Uma visão pode ter formato de inteiro, ponto flutuante ou outro tipo suportado pelo exportador. O valor passado para count deve ser compatível com os elementos da visão.
from array import array
amostras = array("i", [10, 20, 10, 30, 10])
visao = memoryview(amostras)
print(visao.format)
print(visao.count(10))
Nesse caso, a unidade de comparação é um inteiro da matriz, não cada byte que compõe o inteiro. Isso torna o método útil para séries numéricas e buffers tipados.
Cast para outra interpretação
O método cast pode reinterpretar a mesma memória com outro formato quando as regras de tamanho e alinhamento permitem.
dados = bytearray([1, 0, 1, 0, 2, 0])
visao = memoryview(dados)
inteiros = visao.cast("H")
print(inteiros.count(1))
A interpretação depende da plataforma e do formato escolhido. Em dados persistidos ou transmitidos, trate explicitamente endianness e layout. Não presuma que um cast nativo produz o mesmo resultado em todos os ambientes.
Visões somente leitura e graváveis
count funciona como operação de leitura. Ele pode ser usado tanto em uma visão de bytes quanto em uma visão de bytearray. A diferença aparece quando outras partes do programa alteram o buffer durante o processamento.
Em código concorrente, memória compartilhada pode mudar entre duas leituras. Se você precisa de uma fotografia imutável, uma cópia deliberada pode ser necessária. Zero cópia é uma otimização, não uma garantia automática de consistência.
Buffers multidimensionais
Alguns exportadores expõem forma e dimensões. Antes de contar, verifique ndim, shape, strides e contiguous. Operações podem exigir uma visão compatível e contígua. Para dados científicos, confirme se a contagem percorre os elementos na representação esperada.
def descrever(visao):
print("formato:", visao.format)
print("dimensoes:", visao.ndim)
print("shape:", visao.shape)
print("contigua:", visao.contiguous)
Essa inspeção evita assumir que qualquer buffer é uma lista linear simples.
Exemplo com arquivo mapeado
Arquivos mapeados permitem acessar grandes arquivos como memória virtual. Uma visão pode contar marcadores sem carregar manualmente o arquivo inteiro em uma nova sequência.
import mmap
with open("dados.bin", "rb") as arquivo:
with mmap.mmap(arquivo.fileno(), 0, access=mmap.ACCESS_READ) as mapa:
visao = memoryview(mapa)
try:
print(visao.count(0))
finally:
visao.release()
Libere a visão antes de fechar o mapa. Caso contrário, o Python pode impedir o fechamento porque ainda existe um exportador ativo.
Liberação e ciclo de vida
Uma visão mantém uma referência ao objeto subjacente. Em recursos como mmap e memória compartilhada, use release ou um gerenciador de contexto para encerrar o acesso no momento correto.
with memoryview(bytearray(b"abcabc")) as visao:
print(visao.count(ord("a")))
Depois da liberação, não tente reutilizar a visão.
Validação de entrada
Ao contar valores em dados externos, limite o tamanho aceito, valide offsets e confirme o formato. Uma contagem não substitui parser, autenticação ou verificação de integridade. Um arquivo malformado pode conter milhões de delimitadores e ainda assim violar completamente o formato esperado.
def contar_delimitador(buffer, valor, limite=10_000_000):
visao = memoryview(buffer)
if visao.nbytes > limite:
raise ValueError("buffer acima do limite")
if not 0 <= valor <= 255:
raise ValueError("valor de byte inválido")
return visao.cast("B").count(valor)
Desempenho e medição
Não presuma que zero cópia sempre é mais rápido em qualquer cenário. Meça com dados representativos, descarte aquecimento, repita a operação e observe memória além do tempo. Para uma única contagem em poucos bytes, a diferença será pequena. Para buffers grandes processados repetidamente, evitar conversões pode ser importante.
Erros comuns
Um erro frequente é passar uma string, como "A", para uma visão de bytes. O elemento esperado é um inteiro. Outro erro é confundir quantidade de bytes com quantidade de elementos em uma visão tipada. Também é comum manter uma visão ativa e tentar redimensionar o bytearray original, o que pode gerar exceção.
Boas práticas
Inspecione o formato antes de contar, use cast("B") apenas quando quiser interpretar a memória como bytes, libere visões ligadas a recursos externos, valide limites e documente se o valor representa byte, inteiro, amostra ou outra unidade. Prefira uma cópia explícita quando consistência for mais importante que economia de memória.
Conteúdos relacionados
Veja também os guias da Academify sobre bytes no Python, bytearray, arquivos binários e mmap. Consulte ainda a documentação oficial de memoryview e a documentação do protocolo de buffer.
Conclusão
memoryview.count é uma ferramenta direta para contar elementos em buffers sem criar representações intermediárias desnecessárias. Seu valor aparece principalmente quando o código precisa trabalhar com grandes blocos binários, fatias, memória mapeada ou sequências tipadas. Use o método junto com inspeção de formato, validação de limites e controle do ciclo de vida para obter desempenho sem perder previsibilidade.







