Lendo arquivo binário e fazendo loop em cada byte


377

No Python, como leio em um arquivo binário e faço um loop sobre cada byte desse arquivo?

Respostas:


387

Python 2.4 e anteriores

f = open("myfile", "rb")
try:
    byte = f.read(1)
    while byte != "":
        # Do stuff with byte.
        byte = f.read(1)
finally:
    f.close()

Python 2.5-2.7

with open("myfile", "rb") as f:
    byte = f.read(1)
    while byte != "":
        # Do stuff with byte.
        byte = f.read(1)

Observe que a instrução with não está disponível nas versões do Python abaixo de 2.5. Para usá-lo na v 2.5, você precisará importá-lo:

from __future__ import with_statement

Na versão 2.6, isso não é necessário.

Python 3

No Python 3, é um pouco diferente. Não vamos mais obter caracteres brutos do fluxo no modo de byte, mas objetos de byte; portanto, precisamos alterar a condição:

with open("myfile", "rb") as f:
    byte = f.read(1)
    while byte != b"":
        # Do stuff with byte.
        byte = f.read(1)

Ou, como Benhoyt diz, pule o que não é igual e tire proveito do fato de que b""avalia como falso. Isso torna o código compatível entre 2.6 e 3.x sem nenhuma alteração. Isso também evitaria que você alterasse a condição se passar do modo de byte para texto ou vice-versa.

with open("myfile", "rb") as f:
    byte = f.read(1)
    while byte:
        # Do stuff with byte.
        byte = f.read(1)

python 3.8

A partir de agora, graças ao operador: =, o código acima pode ser escrito de forma mais curta.

with open("myfile", "rb") as f:
    while (byte := f.read(1)):
        # Do stuff with byte.

40
Ler um arquivo em bytes é um pesadelo de desempenho. Esta não pode ser a melhor solução disponível em python. Este código deve ser usado com cuidado.
usr

7
@ usr: Bem, os objetos de arquivo são armazenados em buffer internamente, e mesmo assim foi o que foi solicitado. Nem todo script precisa de um desempenho ideal.
21312 Skurmedel

4
@mezhaka: Então você muda de read (1) para read (bufsize) e no loop while você faz um for-in ... o exemplo ainda permanece.
Skurmedel

3
@ usr: a diferença de desempenho pode ser de até 200 vezes para o código que eu tentei .
JFS

2
@usr - depende de quantos bytes você deseja processar. Se forem poucos o suficiente, código com desempenho "ruim", mas facilmente compreensível, pode ser muito preferido. O desperdício de ciclos da CPU é compensado por salvar "ciclos da CPU do leitor" ao manter o código.
precisa saber é o seguinte

172

Este gerador gera bytes de um arquivo, lendo o arquivo em pedaços:

def bytes_from_file(filename, chunksize=8192):
    with open(filename, "rb") as f:
        while True:
            chunk = f.read(chunksize)
            if chunk:
                for b in chunk:
                    yield b
            else:
                break

# example:
for b in bytes_from_file('filename'):
    do_stuff_with(b)

Consulte a documentação do Python para obter informações sobre iteradores e geradores .


3
@ codeape Apenas o que eu estou procurando. Mas, como você determina o tamanho do pedaço? Pode ser um valor arbitrário?
swdev

3
@swdev: O exemplo usa um tamanho de pedaço de 8192 bytes . O parâmetro para a função file.read () - simplesmente especifica o tamanho, ou seja, o número de bytes a serem lidos. codeape escolheu 8192 Byte = 8 kB(na verdade é, KiBmas isso não é tão conhecido). O valor é "totalmente" aleatória, mas 8 kB parece ser um valor adequado: não muita memória é desperdiçado e ainda não existem "muitas" as operações de leitura como na resposta aceita por Skurmedel ...
mozzbozz

3
O sistema de arquivos já armazena em buffer pedaços de dados, portanto esse código é redundante. É melhor ler um byte de cada vez.
Stark

17
Embora já seja mais rápido que a resposta aceita, isso pode ser acelerado em outros 20 a 25%, substituindo-o por todo o for b in chunk:loop mais interno yield from chunk. Esta forma de yieldfoi adicionada no Python 3.3 (consulte Expressões de rendimento ).
martineau 14/02

3
Hmm parece improvável, link?
Codeape 18/07/19

54

Se o arquivo não for muito grande, é um problema mantê-lo na memória:

with open("filename", "rb") as f:
    bytes_read = f.read()
for b in bytes_read:
    process_byte(b)

onde process_byte representa alguma operação que você deseja executar no byte passado.

Se você deseja processar um pedaço de cada vez:

with open("filename", "rb") as f:
    bytes_read = f.read(CHUNKSIZE)
    while bytes_read:
        for b in bytes_read:
            process_byte(b)
        bytes_read = f.read(CHUNKSIZE)

A withdeclaração está disponível no Python 2.5 e superior.


11
Você pode estar interessado no benchmark que acabei de publicar.
martineau

37

Para ler um arquivo - um byte de cada vez (ignorando o tampão) - você poderia usar o dois-argumento iter(callable, sentinel)construído em função de :

with open(filename, 'rb') as file:
    for byte in iter(lambda: file.read(1), b''):
        # Do stuff with byte

Ele chama file.read(1)até retornar nada b''(bytestring vazio). A memória não cresce ilimitada para arquivos grandes. Você pode passar buffering=0 para open(), para desativar o buffer - garante que apenas um byte seja lido por iteração (lento).

withA instrução fecha o arquivo automaticamente - incluindo o caso em que o código abaixo gera uma exceção.

Apesar da presença de buffer interno por padrão, ainda é ineficiente processar um byte de cada vez. Por exemplo, aqui está o blackhole.pyutilitário que come tudo o que é dado:

#!/usr/bin/env python3
"""Discard all input. `cat > /dev/null` analog."""
import sys
from functools import partial
from collections import deque

chunksize = int(sys.argv[1]) if len(sys.argv) > 1 else (1 << 15)
deque(iter(partial(sys.stdin.detach().read, chunksize), b''), maxlen=0)

Exemplo:

$ dd if=/dev/zero bs=1M count=1000 | python3 blackhole.py

Ele processa ~ 1,5 GB / s quando estáchunksize == 32768 na minha máquina e apenas ~ 7,5 MB / s quando chunksize == 1. Ou seja, é 200 vezes mais lento ler um byte de cada vez. Leve isso em consideração se você puder reescrever seu processamento para usar mais de um byte por vez e se precisar de desempenho.

mmappermite tratar um arquivo como bytearraye um objeto de arquivo simultaneamente. Pode servir como uma alternativa para carregar o arquivo inteiro na memória, se você precisar acessar as duas interfaces. Em particular, você pode iterar um byte de cada vez em um arquivo mapeado na memória usando apenas um forloop simples :

from mmap import ACCESS_READ, mmap

with open(filename, 'rb', 0) as f, mmap(f.fileno(), 0, access=ACCESS_READ) as s:
    for byte in s: # length is equal to the current file size
        # Do stuff with byte

mmapsuporta a notação de fatia. Por exemplo, mm[i:i+len]retorna lenbytes do arquivo começando na posição i. O protocolo do gerenciador de contexto não é suportado antes do Python 3.2; você precisa ligar mm.close()explicitamente nesse caso. A iteração de cada byte usando mmapconsome mais memória que file.read(1), mas mmapé uma ordem de magnitude mais rápida.


Achei o último exemplo muito interessante. Pena que não há numpymatrizes equivalentes mapeadas na memória (bytes).
martineau

11
@martineau existe numpy.memmap()e você pode obter os dados um byte por vez (ctypes.data). Você pode pensar em matrizes numpy como um pouco mais do que blobs na memória + metadados.
JFS

jfs: Obrigado, excelentes notícias! Não sabia que isso existia. Ótima resposta, entre.
martineau

25

Lendo arquivo binário em Python e fazendo loop em cada byte

O novo pathlibmódulo do Python 3.5 é o módulo, que possui um método de conveniência específico para ler um arquivo como bytes, permitindo a iteração sobre os bytes. Considero isso uma resposta decente (se rápida e suja):

import pathlib

for byte in pathlib.Path(path).read_bytes():
    print(byte)

Interessante que esta é a única resposta a ser mencionada pathlib.

No Python 2, você provavelmente faria isso (como Vinay Sajip também sugere):

with open(path, 'b') as file:
    for byte in file.read():
        print(byte)

No caso de o arquivo ser muito grande para iterar na memória, você o agruparia, idiomaticamente, usando a iterfunção com a callable, sentinelassinatura - a versão do Python 2:

with open(path, 'b') as file:
    callable = lambda: file.read(1024)
    sentinel = bytes() # or b''
    for chunk in iter(callable, sentinel): 
        for byte in chunk:
            print(byte)

(Várias outras respostas mencionam isso, mas poucas oferecem um tamanho de leitura razoável.)

Prática recomendada para arquivos grandes ou leitura em buffer / interativa

Vamos criar uma função para fazer isso, incluindo usos idiomáticos da biblioteca padrão para Python 3.5+:

from pathlib import Path
from functools import partial
from io import DEFAULT_BUFFER_SIZE

def file_byte_iterator(path):
    """given a path, return an iterator over the file
    that lazily loads the file
    """
    path = Path(path)
    with path.open('rb') as file:
        reader = partial(file.read1, DEFAULT_BUFFER_SIZE)
        file_iterator = iter(reader, bytes())
        for chunk in file_iterator:
            yield from chunk

Note que usamos file.read1. file.readblocos até obter todos os bytes solicitados ou EOF. file.read1nos permite evitar o bloqueio, e ele pode retornar mais rapidamente por causa disso. Nenhuma outra resposta menciona isso também.

Demonstração do uso das melhores práticas:

Vamos criar um arquivo com um megabyte (na verdade mebibyte) de dados pseudo-aleatórios:

import random
import pathlib
path = 'pseudorandom_bytes'
pathobj = pathlib.Path(path)

pathobj.write_bytes(
  bytes(random.randint(0, 255) for _ in range(2**20)))

Agora vamos iterar sobre ele e materializá-lo na memória:

>>> l = list(file_byte_iterator(path))
>>> len(l)
1048576

Podemos inspecionar qualquer parte dos dados, por exemplo, os últimos 100 e os primeiros 100 bytes:

>>> l[-100:]
[208, 5, 156, 186, 58, 107, 24, 12, 75, 15, 1, 252, 216, 183, 235, 6, 136, 50, 222, 218, 7, 65, 234, 129, 240, 195, 165, 215, 245, 201, 222, 95, 87, 71, 232, 235, 36, 224, 190, 185, 12, 40, 131, 54, 79, 93, 210, 6, 154, 184, 82, 222, 80, 141, 117, 110, 254, 82, 29, 166, 91, 42, 232, 72, 231, 235, 33, 180, 238, 29, 61, 250, 38, 86, 120, 38, 49, 141, 17, 190, 191, 107, 95, 223, 222, 162, 116, 153, 232, 85, 100, 97, 41, 61, 219, 233, 237, 55, 246, 181]
>>> l[:100]
[28, 172, 79, 126, 36, 99, 103, 191, 146, 225, 24, 48, 113, 187, 48, 185, 31, 142, 216, 187, 27, 146, 215, 61, 111, 218, 171, 4, 160, 250, 110, 51, 128, 106, 3, 10, 116, 123, 128, 31, 73, 152, 58, 49, 184, 223, 17, 176, 166, 195, 6, 35, 206, 206, 39, 231, 89, 249, 21, 112, 168, 4, 88, 169, 215, 132, 255, 168, 129, 127, 60, 252, 244, 160, 80, 155, 246, 147, 234, 227, 157, 137, 101, 84, 115, 103, 77, 44, 84, 134, 140, 77, 224, 176, 242, 254, 171, 115, 193, 29]

Não itere por linhas para arquivos binários

Não faça o seguinte - isso puxa um pedaço de tamanho arbitrário até chegar a um caractere de nova linha - muito lento quando os pedaços são muito pequenos e, possivelmente, muito grandes também:

    with open(path, 'rb') as file:
        for chunk in file: # text newline iteration - not for bytes
            yield from chunk

O que foi dito acima é bom apenas para arquivos de texto legíveis semanticamente humanos (como texto sem formatação, código, marcação, remarcação etc ... essencialmente qualquer coisa codificada como ascii, utf, latin, etc ...) que você deve abrir sem a 'b'bandeira.


2
Isso é muito melhor ... obrigado por fazer isso. Sei que nem sempre é divertido voltar a uma resposta de dois anos, mas agradeço que você tenha feito isso. Eu particularmente gosto do "Não iterate por linhas" subposição :-)
Floris

11
Oi Aaron, existe alguma razão pela qual você escolheu usar, em path = Path(path), with path.open('rb') as file:vez de usar a função aberta integrada? Ambos fazem a mesma coisa correta?
Joshua Yonathan

11
@ JoshuaYonathan Eu uso o Pathobjeto porque é uma nova maneira muito conveniente de lidar com caminhos. Em vez de passar uma string para as funções "certas" cuidadosamente escolhidas, podemos simplesmente chamar os métodos no objeto path, que contém essencialmente a maior parte da funcionalidade importante que você deseja com o que é semanticamente uma string de caminho. Com IDEs que podem inspecionar, também podemos obter o preenchimento automático com mais facilidade. Poderíamos fazer o mesmo com o openbuilt-in, mas há muitas vantagens ao escrever o programa para o programador usar o Pathobjeto.
Aaron Hall

11
O último método que você mencionou usando a função file_byte_iteratoré muito mais rápido que todos os métodos que tentei nesta página. Parabéns a você!
Rick M.

@ RickM: Você pode estar interessado no benchmark que acabei de publicar.
martineau

19

Para resumir todos os pontos brilhantes de chrispy, Skurmedel, Ben Hoyt e Peter Hansen, essa seria a solução ideal para processar um arquivo binário, um byte de cada vez:

with open("myfile", "rb") as f:
    while True:
        byte = f.read(1)
        if not byte:
            break
        do_stuff_with(ord(byte))

Para python versões 2.6 e acima, porque:

  • buffers python internamente - não é necessário ler pedaços
  • Princípio DRY - não repita a linha de leitura
  • com a instrução garante que um arquivo limpo seja fechado
  • 'byte' é avaliado como false quando não há mais bytes (não quando um byte é zero)

Ou use a solução JF Sebastians para aumentar a velocidade

from functools import partial

with open(filename, 'rb') as file:
    for byte in iter(partial(file.read, 1), b''):
        # Do stuff with byte

Ou se você quiser como uma função geradora, como demonstrado pelo codeape:

def bytes_from_file(filename):
    with open(filename, "rb") as f:
        while True:
            byte = f.read(1)
            if not byte:
                break
            yield(ord(byte))

# example:
for b in bytes_from_file('filename'):
    do_stuff_with(b)

2
Como a resposta vinculada diz, a leitura / processamento de um byte de cada vez ainda é lento no Python, mesmo que as leituras sejam armazenadas em buffer. O desempenho pode ser melhorado drasticamente se vários bytes de cada vez puderem ser processados, como no exemplo da resposta vinculada: 1,5 GB / s vs. 7,5 MB / s.
JFS

6

Python 3, leia todo o arquivo de uma vez:

with open("filename", "rb") as binary_file:
    # Read the whole file at once
    data = binary_file.read()
    print(data)

Você pode iterar o que quiser usando a datavariável.


6

Depois de tentar todas as opções acima e usar a resposta do @Aaron Hall, eu estava recebendo erros de memória para um arquivo de ~ 90 Mb em um computador executando Windows 10, 8 Gb de RAM e Python 3.5 de 32 bits. Fui recomendado por um colega para usar numpye ele faz maravilhas.

De longe, o mais rápido para ler um arquivo binário inteiro (que eu testei) é:

import numpy as np

file = "binary_file.bin"
data = np.fromfile(file, 'u1')

Referência

Multidões mais rápido do que qualquer outro método até agora. Espero que ajude alguém!


3
Bom, mas não pode ser usado em arquivos binários que contêm tipos de dados diferentes.
Nirmal

@Nirmal: A pergunta é sobre loop sobre o byte de alcance, por isso não está claro se o seu comentário sobre diferentes tipos de dados tem alguma influência.
martineau

11
Rick: Seu código não está fazendo a mesma coisa que os outros - ou seja, repetindo cada byte. Se isso é adicionado, não é mais rápido que a maioria dos outros, pelo menos de acordo com os resultados no meu benchmark . De fato, parece ser uma das abordagens mais lentas. Se o processamento feito para cada byte (seja o que for) for algo que possa ser feito via numpy, poderá valer a pena.
27519 martineau

@martineau Obrigado por seus comentários, sim, eu entendo que a pergunta é sobre repetir cada byte e não apenas carregar tudo de uma só vez, mas há outras respostas nessa pergunta que também apontam para a leitura de todo o conteúdo e, portanto, minha resposta
Rick M.

4

Se você tiver muitos dados binários para ler, considere o módulo struct . Ele está documentado como convertendo "entre tipos C e Python", mas é claro que bytes são bytes, e se esses foram criados como tipos C não importa. Por exemplo, se seus dados binários contiverem dois inteiros de 2 bytes e um inteiro de 4 bytes, você poderá lê-los da seguinte maneira (exemplo extraído da structdocumentação):

>>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)

Você pode achar isso mais conveniente, mais rápido ou ambos, do que fazer um loop explícito no conteúdo de um arquivo.


4

Este post em si não é uma resposta direta à pergunta. Em vez disso, é um benchmark extensível orientado a dados que pode ser usado para comparar muitas das respostas (e variações da utilização de novos recursos adicionados em versões posteriores e mais modernas do Python) postadas nesta pergunta - e, portanto, devem ser seja útil para determinar qual é o melhor desempenho.

Em alguns casos, modifiquei o código na resposta referenciada para torná-lo compatível com a estrutura de benchmark.

Primeiro, aqui estão os resultados para as atuais versões mais recentes do Python 2 e 3:

Fastest to slowest execution speeds with 32-bit Python 2.7.16
  numpy version 1.16.5
  Test file size: 1,024 KiB
  100 executions, best of 3 repetitions

1                  Tcll (array.array) :   3.8943 secs, rel speed   1.00x,   0.00% slower (262.95 KiB/sec)
2  Vinay Sajip (read all into memory) :   4.1164 secs, rel speed   1.06x,   5.71% slower (248.76 KiB/sec)
3            codeape + iter + partial :   4.1616 secs, rel speed   1.07x,   6.87% slower (246.06 KiB/sec)
4                             codeape :   4.1889 secs, rel speed   1.08x,   7.57% slower (244.46 KiB/sec)
5               Vinay Sajip (chunked) :   4.1977 secs, rel speed   1.08x,   7.79% slower (243.94 KiB/sec)
6           Aaron Hall (Py 2 version) :   4.2417 secs, rel speed   1.09x,   8.92% slower (241.41 KiB/sec)
7                     gerrit (struct) :   4.2561 secs, rel speed   1.09x,   9.29% slower (240.59 KiB/sec)
8                     Rick M. (numpy) :   8.1398 secs, rel speed   2.09x, 109.02% slower (125.80 KiB/sec)
9                           Skurmedel :  31.3264 secs, rel speed   8.04x, 704.42% slower ( 32.69 KiB/sec)

Benchmark runtime (min:sec) - 03:26

Fastest to slowest execution speeds with 32-bit Python 3.8.0
  numpy version 1.17.4
  Test file size: 1,024 KiB
  100 executions, best of 3 repetitions

1  Vinay Sajip + "yield from" + "walrus operator" :   3.5235 secs, rel speed   1.00x,   0.00% slower (290.62 KiB/sec)
2                       Aaron Hall + "yield from" :   3.5284 secs, rel speed   1.00x,   0.14% slower (290.22 KiB/sec)
3         codeape + iter + partial + "yield from" :   3.5303 secs, rel speed   1.00x,   0.19% slower (290.06 KiB/sec)
4                      Vinay Sajip + "yield from" :   3.5312 secs, rel speed   1.00x,   0.22% slower (289.99 KiB/sec)
5      codeape + "yield from" + "walrus operator" :   3.5370 secs, rel speed   1.00x,   0.38% slower (289.51 KiB/sec)
6                          codeape + "yield from" :   3.5390 secs, rel speed   1.00x,   0.44% slower (289.35 KiB/sec)
7                                      jfs (mmap) :   4.0612 secs, rel speed   1.15x,  15.26% slower (252.14 KiB/sec)
8              Vinay Sajip (read all into memory) :   4.5948 secs, rel speed   1.30x,  30.40% slower (222.86 KiB/sec)
9                        codeape + iter + partial :   4.5994 secs, rel speed   1.31x,  30.54% slower (222.64 KiB/sec)
10                                        codeape :   4.5995 secs, rel speed   1.31x,  30.54% slower (222.63 KiB/sec)
11                          Vinay Sajip (chunked) :   4.6110 secs, rel speed   1.31x,  30.87% slower (222.08 KiB/sec)
12                      Aaron Hall (Py 2 version) :   4.6292 secs, rel speed   1.31x,  31.38% slower (221.20 KiB/sec)
13                             Tcll (array.array) :   4.8627 secs, rel speed   1.38x,  38.01% slower (210.58 KiB/sec)
14                                gerrit (struct) :   5.0816 secs, rel speed   1.44x,  44.22% slower (201.51 KiB/sec)
15                 Rick M. (numpy) + "yield from" :  11.8084 secs, rel speed   3.35x, 235.13% slower ( 86.72 KiB/sec)
16                                      Skurmedel :  11.8806 secs, rel speed   3.37x, 237.18% slower ( 86.19 KiB/sec)
17                                Rick M. (numpy) :  13.3860 secs, rel speed   3.80x, 279.91% slower ( 76.50 KiB/sec)

Benchmark runtime (min:sec) - 04:47

Também o executei com um arquivo de teste de 10 MiB muito maior (que levou quase uma hora para ser executado) e obtive resultados de desempenho comparáveis ​​aos mostrados acima.

Aqui está o código usado para fazer o benchmarking:

from __future__ import print_function
import array
import atexit
from collections import deque, namedtuple
import io
from mmap import ACCESS_READ, mmap
import numpy as np
from operator import attrgetter
import os
import random
import struct
import sys
import tempfile
from textwrap import dedent
import time
import timeit
import traceback

try:
    xrange
except NameError:  # Python 3
    xrange = range


class KiB(int):
    """ KibiBytes - multiples of the byte units for quantities of information. """
    def __new__(self, value=0):
        return 1024*value


BIG_TEST_FILE = 1  # MiBs or 0 for a small file.
SML_TEST_FILE = KiB(64)
EXECUTIONS = 100  # Number of times each "algorithm" is executed per timing run.
TIMINGS = 3  # Number of timing runs.
CHUNK_SIZE = KiB(8)
if BIG_TEST_FILE:
    FILE_SIZE = KiB(1024) * BIG_TEST_FILE
else:
    FILE_SIZE = SML_TEST_FILE  # For quicker testing.

# Common setup for all algorithms -- prefixed to each algorithm's setup.
COMMON_SETUP = dedent("""
    # Make accessible in algorithms.
    from __main__ import array, deque, get_buffer_size, mmap, np, struct
    from __main__ import ACCESS_READ, CHUNK_SIZE, FILE_SIZE, TEMP_FILENAME
    from functools import partial
    try:
        xrange
    except NameError:  # Python 3
        xrange = range
""")


def get_buffer_size(path):
    """ Determine optimal buffer size for reading files. """
    st = os.stat(path)
    try:
        bufsize = st.st_blksize # Available on some Unix systems (like Linux)
    except AttributeError:
        bufsize = io.DEFAULT_BUFFER_SIZE
    return bufsize

# Utility primarily for use when embedding additional algorithms into benchmark.
VERIFY_NUM_READ = """
    # Verify generator reads correct number of bytes (assumes values are correct).
    bytes_read = sum(1 for _ in file_byte_iterator(TEMP_FILENAME))
    assert bytes_read == FILE_SIZE, \
           'Wrong number of bytes generated: got {:,} instead of {:,}'.format(
                bytes_read, FILE_SIZE)
"""

TIMING = namedtuple('TIMING', 'label, exec_time')

class Algorithm(namedtuple('CodeFragments', 'setup, test')):

    # Default timeit "stmt" code fragment.
    _TEST = """
        #for b in file_byte_iterator(TEMP_FILENAME):  # Loop over every byte.
        #    pass  # Do stuff with byte...
        deque(file_byte_iterator(TEMP_FILENAME), maxlen=0)  # Data sink.
    """

    # Must overload __new__ because (named)tuples are immutable.
    def __new__(cls, setup, test=None):
        """ Dedent (unindent) code fragment string arguments.
        Args:
          `setup` -- Code fragment that defines things used by `test` code.
                     In this case it should define a generator function named
                     `file_byte_iterator()` that will be passed that name of a test file
                     of binary data. This code is not timed.
          `test` -- Code fragment that uses things defined in `setup` code.
                    Defaults to _TEST. This is the code that's timed.
        """
        test =  cls._TEST if test is None else test  # Use default unless one is provided.

        # Uncomment to replace all performance tests with one that verifies the correct
        # number of bytes values are being generated by the file_byte_iterator function.
        #test = VERIFY_NUM_READ

        return tuple.__new__(cls, (dedent(setup), dedent(test)))


algorithms = {

    'Aaron Hall (Py 2 version)': Algorithm("""
        def file_byte_iterator(path):
            with open(path, "rb") as file:
                callable = partial(file.read, 1024)
                sentinel = bytes() # or b''
                for chunk in iter(callable, sentinel):
                    for byte in chunk:
                        yield byte
    """),

    "codeape": Algorithm("""
        def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
            with open(filename, "rb") as f:
                while True:
                    chunk = f.read(chunksize)
                    if chunk:
                        for b in chunk:
                            yield b
                    else:
                        break
    """),

    "codeape + iter + partial": Algorithm("""
        def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
            with open(filename, "rb") as f:
                for chunk in iter(partial(f.read, chunksize), b''):
                    for b in chunk:
                        yield b
    """),

    "gerrit (struct)": Algorithm("""
        def file_byte_iterator(filename):
            with open(filename, "rb") as f:
                fmt = '{}B'.format(FILE_SIZE)  # Reads entire file at once.
                for b in struct.unpack(fmt, f.read()):
                    yield b
    """),

    'Rick M. (numpy)': Algorithm("""
        def file_byte_iterator(filename):
            for byte in np.fromfile(filename, 'u1'):
                yield byte
    """),

    "Skurmedel": Algorithm("""
        def file_byte_iterator(filename):
            with open(filename, "rb") as f:
                byte = f.read(1)
                while byte:
                    yield byte
                    byte = f.read(1)
    """),

    "Tcll (array.array)": Algorithm("""
        def file_byte_iterator(filename):
            with open(filename, "rb") as f:
                arr = array.array('B')
                arr.fromfile(f, FILE_SIZE)  # Reads entire file at once.
                for b in arr:
                    yield b
    """),

    "Vinay Sajip (read all into memory)": Algorithm("""
        def file_byte_iterator(filename):
            with open(filename, "rb") as f:
                bytes_read = f.read()  # Reads entire file at once.
            for b in bytes_read:
                yield b
    """),

    "Vinay Sajip (chunked)": Algorithm("""
        def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
            with open(filename, "rb") as f:
                chunk = f.read(chunksize)
                while chunk:
                    for b in chunk:
                        yield b
                    chunk = f.read(chunksize)
    """),

}  # End algorithms

#
# Versions of algorithms that will only work in certain releases (or better) of Python.
#
if sys.version_info >= (3, 3):
    algorithms.update({

        'codeape + iter + partial + "yield from"': Algorithm("""
            def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
                with open(filename, "rb") as f:
                    for chunk in iter(partial(f.read, chunksize), b''):
                        yield from chunk
        """),

        'codeape + "yield from"': Algorithm("""
            def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
                with open(filename, "rb") as f:
                    while True:
                        chunk = f.read(chunksize)
                        if chunk:
                            yield from chunk
                        else:
                            break
        """),

        "jfs (mmap)": Algorithm("""
            def file_byte_iterator(filename):
                with open(filename, "rb") as f, \
                     mmap(f.fileno(), 0, access=ACCESS_READ) as s:
                    yield from s
        """),

        'Rick M. (numpy) + "yield from"': Algorithm("""
            def file_byte_iterator(filename):
            #    data = np.fromfile(filename, 'u1')
                yield from np.fromfile(filename, 'u1')
        """),

        'Vinay Sajip + "yield from"': Algorithm("""
            def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
                with open(filename, "rb") as f:
                    chunk = f.read(chunksize)
                    while chunk:
                        yield from chunk  # Added in Py 3.3
                        chunk = f.read(chunksize)
        """),

    })  # End Python 3.3 update.

if sys.version_info >= (3, 5):
    algorithms.update({

        'Aaron Hall + "yield from"': Algorithm("""
            from pathlib import Path

            def file_byte_iterator(path):
                ''' Given a path, return an iterator over the file
                    that lazily loads the file.
                '''
                path = Path(path)
                bufsize = get_buffer_size(path)

                with path.open('rb') as file:
                    reader = partial(file.read1, bufsize)
                    for chunk in iter(reader, bytes()):
                        yield from chunk
        """),

    })  # End Python 3.5 update.

if sys.version_info >= (3, 8, 0):
    algorithms.update({

        'Vinay Sajip + "yield from" + "walrus operator"': Algorithm("""
            def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
                with open(filename, "rb") as f:
                    while chunk := f.read(chunksize):
                        yield from chunk  # Added in Py 3.3
        """),

        'codeape + "yield from" + "walrus operator"': Algorithm("""
            def file_byte_iterator(filename, chunksize=CHUNK_SIZE):
                with open(filename, "rb") as f:
                    while chunk := f.read(chunksize):
                        yield from chunk
        """),

    })  # End Python 3.8.0 update.update.


#### Main ####

def main():
    global TEMP_FILENAME

    def cleanup():
        """ Clean up after testing is completed. """
        try:
            os.remove(TEMP_FILENAME)  # Delete the temporary file.
        except Exception:
            pass

    atexit.register(cleanup)

    # Create a named temporary binary file of pseudo-random bytes for testing.
    fd, TEMP_FILENAME = tempfile.mkstemp('.bin')
    with os.fdopen(fd, 'wb') as file:
         os.write(fd, bytearray(random.randrange(256) for _ in range(FILE_SIZE)))

    # Execute and time each algorithm, gather results.
    start_time = time.time()  # To determine how long testing itself takes.

    timings = []
    for label in algorithms:
        try:
            timing = TIMING(label,
                            min(timeit.repeat(algorithms[label].test,
                                              setup=COMMON_SETUP + algorithms[label].setup,
                                              repeat=TIMINGS, number=EXECUTIONS)))
        except Exception as exc:
            print('{} occurred timing the algorithm: "{}"\n  {}'.format(
                    type(exc).__name__, label, exc))
            traceback.print_exc(file=sys.stdout)  # Redirect to stdout.
            sys.exit(1)
        timings.append(timing)

    # Report results.
    print('Fastest to slowest execution speeds with {}-bit Python {}.{}.{}'.format(
            64 if sys.maxsize > 2**32 else 32, *sys.version_info[:3]))
    print('  numpy version {}'.format(np.version.full_version))
    print('  Test file size: {:,} KiB'.format(FILE_SIZE // KiB(1)))
    print('  {:,d} executions, best of {:d} repetitions'.format(EXECUTIONS, TIMINGS))
    print()

    longest = max(len(timing.label) for timing in timings)  # Len of longest identifier.
    ranked = sorted(timings, key=attrgetter('exec_time')) # Sort so fastest is first.
    fastest = ranked[0].exec_time
    for rank, timing in enumerate(ranked, 1):
        print('{:<2d} {:>{width}} : {:8.4f} secs, rel speed {:6.2f}x, {:6.2f}% slower '
              '({:6.2f} KiB/sec)'.format(
                    rank,
                    timing.label, timing.exec_time, round(timing.exec_time/fastest, 2),
                    round((timing.exec_time/fastest - 1) * 100, 2),
                    (FILE_SIZE/timing.exec_time) / KiB(1),  # per sec.
                    width=longest))
    print()
    mins, secs = divmod(time.time()-start_time, 60)
    print('Benchmark runtime (min:sec) - {:02d}:{:02d}'.format(int(mins),
                                                               int(round(secs))))

main()

Você está assumindo que yield from chunksim for byte in chunk: yield byte? Estou pensando que devo reforçar minha resposta com isso.
Aaron Hall

@ Aaron: Existem duas versões da sua resposta nos resultados do Python 3 e uma delas usa yield from.
martineau

ok, eu atualizei minha resposta. Também sugiro que você solte, enumeratepois a iteração deve ser concluída - se não, pela última vez que verifiquei - a enumeração tem um pouco de sobrecarga nos custos em fazer a contabilidade do índice com + = 1; portanto, você pode alternativamente fazer a contabilidade em seu próprio código. Ou mesmo passar para um deque com maxlen=0.
Aaron Hall

@ Aaron: Concordo sobre o enumerate. Obrigado pelo feedback. Estarei adicionando uma atualização ao meu post que não a possui (embora eu ache que não mude muito os resultados). Também estará adicionando a numpyresposta baseada em @Rick M.
martineau

Um pouco mais de revisão de código: não acho que faça sentido escrever respostas para o Python 2 neste momento - eu consideraria remover o Python 2, pois eu esperaria que você usasse o Python 3.7 ou 3.8 de 64 bits. Você pode definir a limpeza para finalizar com atexit e um aplicativo parcial. Erro de digitação: "verificar". Não vejo sentido na duplicação das sequências de teste - elas são diferentes? Eu imagino que se você usar em super().vez de tuple.no seu __new__você poderia usar os namedtuplenomes de atributo em vez de índices.
Aaron Hall

3

se você está procurando algo rápido, aqui está um método que eu tenho usado e trabalha há anos:

from array import array

with open( path, 'rb' ) as file:
    data = array( 'B', file.read() ) # buffer the file

# evaluate it's data
for byte in data:
    v = byte # int value
    c = chr(byte)

se você deseja iterar chars em vez de ints, pode simplesmente usar data = file.read(), que deve ser um objeto bytes () em py3.


11
'array' é importado por 'do array import array'
quanly_mc

@quanly_mc sim, obrigado por capturar isso, e desculpe eu esqueci de incluir isso, editando agora.
Tcll 23/07
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.