이진 파일 읽기 및 각 바이트 반복


377

파이썬에서 바이너리 파일을 읽고 해당 파일의 각 바이트를 반복하는 방법은 무엇입니까?

답변:


387

파이썬 2.4 및 그 이전

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

파이썬 2.5-2.7

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

with 문은 2.5 이하의 Python 버전에서는 사용할 수 없습니다. v 2.5에서 사용하려면 가져와야합니다.

from __future__ import with_statement

2.6에서는 필요하지 않습니다.

파이썬 3

파이썬 3에서는 약간 다릅니다. 바이트 모드의 스트림에서 더 이상 원시 문자를 가져 오지 않고 바이트 객체를 가져 오므로 조건을 변경해야합니다.

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

또는 benhoyt가 말한 것처럼 같지 않은 것을 건너 뛰고 b""거짓으로 평가 되는 사실을 이용하십시오 . 이렇게하면 코드를 변경하지 않고 2.6과 3.x 사이에서 호환됩니다. 바이트 모드에서 텍스트 또는 그 반대로 전환하면 조건을 변경하지 않아도됩니다.

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

파이썬 3.8

지금부터 : = 연산자 덕분에 위의 코드를 더 짧게 작성할 수 있습니다.

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

40
파일을 바이트 단위로 읽는 것은 성능 악몽입니다. 이것은 파이썬에서 사용 가능한 최상의 솔루션이 될 수 없습니다. 이 코드는주의해서 사용해야합니다.
usr

7
@ usr : 파일 객체는 내부적으로 버퍼링되어 있으므로 요청 된 것입니다. 모든 스크립트가 최적의 성능을 필요로하는 것은 아닙니다.
Skurmedel

4
@mezhaka : 그래서 당신은 그것을 read (1)에서 read (bufsize)로 바꾸고 while 루프에서 for-in을 수행합니다 ... 예는 여전히 스탠드합니다.
Skurmedel

3
@ usr : 내가 시도한 코드 의 성능 차이는 200 배가 될 수 있습니다 .
jfs

2
@usr-처리하려는 바이트 수에 따라 다릅니다. 그것들이 충분하지 않으면, "나쁘게"수행하지만 이해하기 쉬운 코드가 훨씬 선호 될 수 있습니다. CPU주기 낭비는 코드를 유지 보수 할 때 "판독기 CPU주기"를 절약하기 위해 보상됩니다.
IllvilJa

172

이 생성기는 파일에서 바이트를 생성하여 파일을 청크로 읽습니다.

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)

반복자생성기 에 대한 정보는 Python 문서를 참조하십시오 .


3
@codeape 내가 찾는 것. 그러나 청크 크기를 어떻게 결정합니까? 임의의 값이 될 수 있습니까?
swdev

3
@swdev :이 예제는 8192 바이트 의 청크 크기를 사용합니다 . file.read () 함수의 매개 변수는 단순히 크기, 즉 읽을 바이트 수를 지정합니다. codeape가 선택했습니다 8192 Byte = 8 kB(실제로 KiB알려져 있지만 일반적으로 알려져 있지는 않습니다). 값은 "완전히"임의이지만 8kB는 적절한 값인 것 같습니다. 너무 많은 메모리가 낭비되지 않고 여전히 Skurmedel의 승인 된 답변에서와 같이 "너무 많은"읽기 작업이 없습니다 ...
mozzbozz

3
파일 시스템은 이미 데이터 청크를 버퍼링하므로이 코드는 중복됩니다. 한 번에 바이트를 읽는 것이 좋습니다.
stark

17
이미 허용 된 답변보다 빠르지 만 가장 안쪽 for b in chunk:루프 전체를로 교체하여 20-25 % 더 빨라질 수 있습니다 yield from chunk. 이 형식은 yieldPython 3.3에 추가되었습니다 ( 수율 식 참조 ).
martineau

3
흠.
codeape

54

파일이 너무 커서 메모리에 보관하는 것이 문제가되지 않는 경우 :

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

여기서 process_byte는 전달 된 바이트에서 수행하려는 일부 조작을 나타냅니다.

한 번에 청크를 처리하려는 경우 :

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)

with문장은 Python 2.5 이상에서 사용할 수 있습니다.


1
방금 게시 한 벤치 마크에 관심이있을 수 있습니다 .
martineau

37

버퍼링을 무시하고 한 번에 한 바이트 씩 파일을 읽으려면 두 개의 인수 iter(callable, sentinel)내장 함수를 사용할 수 있습니다 .

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

file.read(1)아무것도 반환하지 않을 때까지 b''(빈 바이트 문자열) 호출 합니다 . 대용량 파일의 경우 메모리가 무제한으로 증가하지 않습니다. 당신은 전달할 수 buffering=0open()버퍼링을 사용하지, - 그것은 단지 한 바이트가 반복 (느린) 당 읽도록 보장한다.

with-statement는 아래 코드에서 예외가 발생하는 경우를 포함하여 파일을 자동으로 닫습니다.

기본적으로 내부 버퍼링이 있지만 한 번에 1 바이트를 처리하는 것은 여전히 ​​비효율적입니다. 예를 들어 blackhole.py다음은 제공된 모든 것을 먹는 유틸리티입니다.

#!/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)

예:

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

그것은 처리 ~ 1.5 GB / s의 경우 chunksize == 32768내 컴퓨터 만에 ~ 7.5 MB / s의 경우 chunksize == 1. 즉, 한 번에 1 바이트를 읽는 것이 200 배 느립니다. 한 번에 둘 이상의 바이트를 사용하도록 처리를 다시 작성할 수 있고 성능이 필요한 경우이를 고려 하십시오.

mmap파일을 bytearray파일 객체로 동시에 처리 할 수 ​​있습니다 . 두 인터페이스에 모두 액세스해야하는 경우 전체 파일을 메모리에로드하는 대신 사용할 수 있습니다. 특히 일반 for루프를 사용하여 메모리 매핑 파일에서 한 번에 한 바이트 씩 반복 할 수 있습니다 .

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

mmap슬라이스 표기법을 지원합니다. 예를 들어, 위치에서 시작하여 파일에서 바이트를 mm[i:i+len]반환 len합니다.i . 컨텍스트 관리자 프로토콜은 Python 3.2 이전에는 지원되지 않습니다. mm.close()이 경우 명시 적 으로 호출해야합니다 . 를 사용하여 각 바이트를 반복 mmap하면보다 많은 메모리 를 소비 file.read(1)하지만 mmap훨씬 빠릅니다.


마지막 예가 매우 흥미로 웠습니다. 너무 나쁜 numpy메모리 매핑 (바이트) 배열이 없습니다.
martineau

1
@ martineau가 있으며 numpy.memmap()한 번에 한 바이트 (ctypes.data)의 데이터를 얻을 수 있습니다. numpy 배열은 메모리 + 메타 데이터의 블롭보다 조금 더 많은 것으로 생각할 수 있습니다.
jfs

jfs : 감사합니다, 훌륭한 뉴스! 그것이 존재하는 것을 몰랐습니다. 좋은 대답입니다, BTW.
martineau

25

파이썬에서 이진 파일을 읽고 각 바이트를 반복

Python 3.5의 새로운 기능인이 pathlib모듈은 파일을 바이트 단위로 읽는 편리한 방법을 제공하므로 바이트를 반복 할 수 있습니다. 나는 이것이 괜찮은 (빠르고 더러운 경우) 대답이라고 생각합니다.

import pathlib

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

이것이 유일한 답변이라는 점에 흥미가 pathlib있습니다.

파이썬 2에서는 Vinay Sajip이 제안한 것처럼 아마도 이렇게 할 것입니다.

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

파일이 메모리 내에서 반복하기에 너무 큰 경우 iter에는 callable, sentinel서명이 있는 함수 ( 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)

(몇 가지 다른 답변이 이것을 언급하지만 합리적인 읽기 크기를 제공하는 사람은 거의 없습니다.)

큰 파일 또는 버퍼 / 대화 형 읽기에 대한 모범 사례

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

우리는를 사용 file.read1합니다. file.read요청 된 모든 바이트를 얻을 때까지 차단하거나EOF . file.read1차단을 피할 수 있으며 이로 인해 더 빨리 돌아올 수 있습니다. 다른 답변들도 이것을 언급하지 않습니다.

모범 사례 사용법 시연 :

메가 바이트 (실제로 mebibyte)의 의사 난수 데이터로 파일을 만들어 봅시다 :

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

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

이제 그것을 반복하고 메모리에 구체화합시다.

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

데이터의 모든 부분 (예 : 마지막 100 바이트와 처음 100 바이트)를 검사 할 수 있습니다.

>>> 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]

이진 파일을 줄 단위로 반복하지 마십시오

다음을 수행하지 마십시오-이것은 줄 바꿈 문자가 될 때까지 임의의 크기의 청크를 가져옵니다. 청크가 너무 작거나 너무 클 때 너무 느립니다.

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

위의 내용은 'b'플래그 없이 열어야하는 의미 적으로 사람이 읽을 수있는 텍스트 파일 (일반 텍스트, 코드, 마크 업, 마크 다운 등과 같은 본질적으로 ASCII, utf, latin 등 ... 인코딩 된 것)에만 적합합니다 .


2
이것은 훨씬 낫습니다 ...이 작업을 수행해 주셔서 감사합니다. 나는 2 살짜리 답변으로 돌아가는 것이 항상 재미있는 것은 아니라는 것을 알고 있지만, 당신이 해냈다는 것에 감사드립니다. 특히 "줄로 반복하지 마십시오"부제목 :-)
Floris

1
안녕하세요 Aaron, path = Path(path), with path.open('rb') as file:대신에 내장 된 공개 함수 를 사용 하지 않고 사용 하기로 선택한 이유가 있습니까? 둘 다 똑같은 일을합니까?
Joshua Yonathan

1
@JoshuaYonathan Path경로를 처리하는 매우 편리한 새로운 방법이기 때문에 객체를 사용합니다. 문자열을 신중하게 선택된 "올바른"함수로 전달하는 대신 경로 객체에서 메소드를 호출 할 수 있습니다. 경로 객체에는 의미 상 경로 문자열과 원하는 대부분의 중요한 기능이 포함되어 있습니다. 검사 할 수있는 IDE를 사용하면 자동 완성 기능을보다 쉽게 ​​얻을 수 있습니다. 우리는 open빌트인으로 똑같이 달성 할 수 있지만 프로그래머가 Path객체를 대신 사용할 수 있도록 프로그램을 작성할 때 많은 단점이 있습니다 .
Aaron Hall

1
함수를 사용하여 언급 한 마지막 방법 file_byte_iterator은이 페이지에서 시도한 모든 방법보다 훨씬 빠릅니다. 당신에게 쿠도스!
Rick M.

@RickM : 방금 게시 한 벤치 마크에 관심이있을 수 있습니다 .
martineau

19

chrispy, Skurmedel, Ben Hoyt 및 Peter Hansen의 모든 훌륭한 요점을 요약하면 이진 파일을 한 번에 한 바이트 씩 처리하는 최적의 솔루션입니다.

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

파이썬 버전 2.6 이상의 경우 :

  • 파이썬 버퍼 내부-청크를 읽을 필요가 없습니다.
  • 건조 원리-판독 라인을 반복하지 마십시오
  • with 문은 깨끗한 파일을 닫습니다.
  • 더 이상 바이트가 없으면 'byte'는 false로 평가됩니다 (바이트가 0이 아닌 경우)

또는 JF Sebastians 솔루션을 사용하여 속도 향상

from functools import partial

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

또는 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
링크 된 답변에서 알 수 있듯이 파이썬에서는 읽기가 버퍼링되어 있어도 한 번에 한 바이트 씩 읽고 처리하는 것이 여전히 느립니다. 링크 된 답변의 예에서와 같이 1.5GB / s 대 7.5MB / s와 같이 한 번에 여러 바이트를 처리 할 수 ​​있으면 성능이 크게 향상 될 수 있습니다.
jfs

6

파이썬 3, 모든 파일을 한 번에 읽으십시오 :

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

data변수를 사용하여 원하는 것을 반복 할 수 있습니다 .


6

위의 모든 것을 시도하고 @Aaron Hall의 답변을 사용한 후 Window 10, 8Gb RAM 및 Python 3.5 32 비트를 실행하는 컴퓨터에서 ~ 90 Mb 파일에 대한 메모리 오류가 발생했습니다. 동료가 사용하는 것이 좋습니다numpy 대신 놀라운 일이 아닙니다.

지금까지 테스트 한 전체 바이너리 파일을 읽는 가장 빠른 방법은 다음과 같습니다.

import numpy as np

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

참고

지금까지 다른 방법보다 훨씬 빠르게 그것이 누군가를 돕기를 바랍니다!


3
훌륭하지만 다른 데이터 형식을 포함하는 이진 파일에는 사용할 수 없습니다.
Nirmal

@Nirmal : 도달 바이트 바이트 반복에 관한 질문이므로 다른 데이터 유형에 대한 의견에 베어링이 있는지 확실하지 않습니다.
martineau

1
Rick : 코드가 다른 코드와 똑같이 작동하지 않습니다. 즉, 각 바이트를 반복합니다. 그것이 추가되면 적어도 내 벤치 마크 결과에 따르면 대다수의 다른 것보다 빠르지 않습니다 . 실제로 그것은 느린 접근법 중 하나 인 것으로 보입니다. 각 바이트에 수행 된 처리 (무엇이든)가를 통해 수행 할 수있는 작업 인 numpy경우 가치가있을 수 있습니다.
martineau

@martineau 귀하의 의견 주셔서 감사합니다, 그래 난이 질문은 각 바이트 이상 반복 그냥 한 번에 모든 것을로드되지 관한 이해 할뿐만 아니라, 지점의 모든 내용 때문에 내 대답을 읽고이 질문에 다른 답변이 있습니다
릭 M.

4

읽을 바이너리 데이터가 많은 경우 struct 모듈 을 고려할 수 있습니다 . "C와 Python 유형 간"변환으로 문서화되어 있지만 바이트는 바이트이며 C 유형으로 작성된 것인지는 중요하지 않습니다. 예를 들어, 이진 데이터에 2 바이트 정수 2 개와 4 바이트 정수 1 개가 포함 된 경우 다음과 같이 읽을 수 있습니다 ( struct문서 에서 가져온 예 ).

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

파일의 내용을 명시 적으로 반복하는 것보다이 방법이 더 편리하거나 빠르거나 둘 다있을 수 있습니다.


4

이 게시물 자체는 질문에 대한 직접적인 답변이 아닙니다. 대신이 질문에 게시 된 많은 답변 (그리고 나중에 최신 버전의 Python에 추가 된 새로운 기능을 사용하는 변형)을 비교하는 데 사용할 수있는 데이터 기반 확장 가능한 벤치 마크입니다. 가장 성능이 좋은 것을 결정하는 데 도움이됩니다.

경우에 따라 벤치 마크 프레임 워크와 호환되도록 참조 답변의 코드를 수정했습니다.

먼저, 현재 최신 버전의 Python 2 & 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

또한 훨씬 더 큰 10 MiB 테스트 파일 (실행하는 데 거의 1 시간이 걸렸습니다)을 사용하여 실행했으며 위에 표시된 것과 비슷한 성능 결과를 얻었습니다.

벤치마킹을 수행하는 데 사용되는 코드는 다음과 같습니다.

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()

내가 yield from chunk대신 한다고 가정 for byte in chunk: yield byte합니까? 나는 그것에 대한 대답을 강화해야한다고 생각합니다.
Aaron Hall

@Aaron : Python 3 결과에는 두 가지 버전이 있으며 그중 하나는을 사용합니다 yield from.
martineau

좋아, 나는 대답을 업데이트했다. 또한 enumerate반복이 완료되어야하는 것으로 이해해야하므로 드롭 하지 않는 것이 좋습니다 . 마지막으로 확인한 경우-열거 형이 + = 1 인 인덱스에 대한 부기 비용보다 약간의 오버 헤드가 있으므로 열 거부에서 부기를 할 수도 있습니다 자신의 코드. 또는로 deque에 전달하십시오 maxlen=0.
Aaron Hall

@Aaron :에 대해 동의하십시오 enumerate. 피드백을 주셔서 감사합니다. 게시물이없는 게시물에 업데이트를 추가 할 예정입니다 (결과가 많이 변경되지는 않더라도). @Rick M.의 numpy답변을 추가 할 예정 입니다.
martineau

좀 더 코드 검토 :이 시점에서 Python 2에 대한 답변을 작성하는 것이 의미가 없다고 생각합니다 .64 비트 Python 3.7 또는 3.8을 사용할 것으로 예상되므로 Python 2를 제거하는 것이 좋습니다. atexit 및 부분 애플리케이션으로 정리가 끝날 수 있도록 설정할 수 있습니다. 오타 : "확인". 테스트 문자열의 복제에는 아무런 의미가 없습니다. 전혀 다릅니다. 당신이 사용하는 경우 상상이 super().대신 tuple.당신에 __new__당신이 사용할 수있는 namedtuple대신 인덱스의 속성 이름.
Aaron Hall

3

빠른 것을 찾고 있다면 몇 년 동안 사용해온 방법이 있습니다.

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)

int 대신 chars를 반복하려면 data = file.read()py3의 bytes () 객체 인을 사용하면됩니다.


1
'배열 가져 오기 배열에서'에 의해 '배열'을 가져옵니다
quanly_mc

@quanly_mc 예, 그것을 잡아 주셔서 감사합니다. 죄송합니다. 지금 편집하는 것을 잊어 버렸습니다.
Tcll
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.