Đọc tệp nhị phân và lặp qua từng byte


377

Trong Python, làm thế nào để tôi đọc trong tệp nhị phân và lặp qua từng byte của tệp đó?

Câu trả lời:


387

Python 2.4 và trước đó

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

Trăn 2,5-2,7

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

Lưu ý rằng câu lệnh with không có sẵn trong các phiên bản Python dưới 2.5. Để sử dụng nó trong phiên bản 2.5, bạn sẽ cần nhập nó:

from __future__ import with_statement

Trong 2.6 điều này là không cần thiết.

Con trăn 3

Trong Python 3, nó hơi khác một chút. Chúng ta sẽ không còn nhận được các ký tự thô từ luồng trong chế độ byte mà là các đối tượng byte, do đó chúng ta cần thay đổi điều kiện:

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

Hoặc như benhoyt nói, bỏ qua không bằng và tận dụng thực tế b""đánh giá là sai. Điều này làm cho mã tương thích giữa 2.6 và 3.x mà không có bất kỳ thay đổi nào. Nó cũng sẽ giúp bạn không thay đổi điều kiện nếu bạn chuyển từ chế độ byte sang văn bản hoặc ngược lại.

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

trăn 3,8

Từ giờ trở đi, nhờ: = toán tử, đoạn mã trên có thể được viết theo cách ngắn hơn.

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

40
Đọc một byte-khôn ngoan là một cơn ác mộng hiệu suất. Đây không thể là giải pháp tốt nhất có sẵn trong python. Mã này nên được sử dụng cẩn thận.
usr

7
@usr: Vâng, các đối tượng tập tin được đệm trong nội bộ, và thậm chí đây là những gì được yêu cầu. Không phải mọi kịch bản đều cần hiệu suất tối ưu.
Skurmedel

4
@mezhaka: Vì vậy, bạn thay đổi nó từ đọc (1) sang đọc (bufsize) và trong vòng lặp while bạn thực hiện một for-in ... ví dụ vẫn đứng.
Skurmedel

3
@usr: sự khác biệt hiệu suất có thể gấp 200 lần cho mã tôi đã thử .
jfs

2
@usr - nó phụ thuộc vào số lượng byte bạn muốn xử lý. Nếu chúng đủ ít, mã "hoạt động kém" nhưng mã dễ hiểu có thể được ưu tiên hơn nhiều. Sự lãng phí các chu kỳ CPU được bù đắp cho việc lưu "chu trình CPU đọc" khi duy trì mã.
IllvilJa

172

Trình tạo này mang lại byte từ một tệp, đọc tệp theo từng đoạn:

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)

Xem tài liệu Python để biết thông tin về các trình vòng lặptrình tạo .


3
@codeape Chỉ là những gì tôi đang tìm kiếm. Nhưng, làm thế nào để bạn xác định chunkize? Nó có thể là một giá trị tùy ý?
swdev

3
@swdev: Ví dụ sử dụng số lượng lớn 8192 Byte . Tham số cho hàm file.read () - chỉ đơn giản chỉ định kích thước, tức là số lượng byte cần đọc. codeape đã chọn 8192 Byte = 8 kB(thực ra là vậy KiBnhưng điều đó không được biết đến nhiều như vậy). Giá trị là "hoàn toàn" ngẫu nhiên nhưng 8 kB dường như là một giá trị phù hợp: không có quá nhiều bộ nhớ bị lãng phí và vẫn không có quá nhiều thao tác đọc như trong câu trả lời được chấp nhận của Skurmedel ...
mozzbozz

3
Hệ thống tập tin đã đệm các khối dữ liệu, vì vậy mã này là dự phòng. Tốt hơn là nên đọc một byte mỗi lần.
stark

17
Mặc dù đã nhanh hơn câu trả lời được chấp nhận, nhưng câu trả lời này có thể được tăng thêm 20-25% bằng cách thay thế toàn bộ for b in chunk:vòng lặp bên trong nhất bằng yield from chunk. Hình thức này yieldđã được thêm vào Python 3.3 (xem Biểu thức Yield ).
martineau 14/2/2016

3
Hmm dường như không thể, liên kết?
codeape

54

Nếu tệp không quá lớn mà việc giữ nó trong bộ nhớ là một vấn đề:

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

trong đó process_byte đại diện cho một số thao tác bạn muốn thực hiện trên byte truyền vào.

Nếu bạn muốn xử lý một đoạn tại một thời điểm:

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)

Câu withlệnh có sẵn trong Python 2.5 trở lên.


1
Bạn có thể quan tâm đến điểm chuẩn tôi vừa đăng.
martineau

37

Để đọc một tệp - một byte mỗi lần (bỏ qua bộ đệm) - bạn có thể sử dụng hàm dựng sẵn hai đối sốiter(callable, sentinel) :

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

Nó gọi file.read(1)cho đến khi nó trả về không có gì b''(bytestring trống). Bộ nhớ không tăng không giới hạn cho các tệp lớn. Bạn có thể chuyển buffering=0 đến open(), để vô hiệu hóa bộ đệm - nó đảm bảo rằng chỉ một byte được đọc trên mỗi lần lặp (chậm).

with-statement đóng tệp tự động - bao gồm cả trường hợp khi mã bên dưới đưa ra một ngoại lệ.

Mặc dù có sự hiện diện của bộ đệm nội bộ theo mặc định, nhưng vẫn không hiệu quả khi xử lý một byte mỗi lần. Ví dụ: đây là blackhole.pytiện ích ăn mọi thứ được cung cấp:

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

Thí dụ:

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

Nó xử lý ~ 1,5 GB / giây khi chunksize == 32768ở trên máy của tôi và chỉ ~ 7,5 MB / s khi chunksize == 1. Đó là, chậm hơn 200 lần để đọc một byte mỗi lần. Hãy tính đến nó nếu bạn có thể viết lại quá trình xử lý của mình để sử dụng nhiều hơn một byte mỗi lần và nếu bạn cần hiệu suất.

mmapcho phép bạn xử lý bytearrayđồng thời một tệp và một đối tượng tệp. Nó có thể phục vụ như là một thay thế để tải toàn bộ tệp trong bộ nhớ nếu bạn cần truy cập cả hai giao diện. Cụ thể, bạn có thể lặp lại một byte mỗi lần trên một tệp ánh xạ bộ nhớ chỉ bằng cách sử dụng một đơn giảnfor :

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

mmaphỗ trợ ký hiệu lát. Ví dụ, mm[i:i+len]trả về lenbyte từ tệp bắt đầu tại vị trí i. Giao thức quản lý bối cảnh không được hỗ trợ trước Python 3.2; bạn cần gọi mm.close()một cách rõ ràng trong trường hợp này. Lặp lại trên mỗi byte sử dụng mmapsẽ tiêu tốn nhiều bộ nhớ hơn file.read(1), nhưng mmaplà một thứ tự cường độ nhanh hơn.


Tôi tìm thấy ví dụ cuối cùng rất thú vị. Quá tệ, không có numpymảng (byte) ánh xạ bộ nhớ tương đương .
martineau

1
@martineau có numpy.memmap()và bạn có thể nhận được một byte dữ liệu tại một thời điểm (ctypes.data). Bạn có thể nghĩ về các mảng numpy chỉ hơn một chút so với các đốm trong bộ nhớ + siêu dữ liệu.
jfs

jfs: Cảm ơn, tin tuyệt vời! Không biết nó tồn tại như thế nào. Câu trả lời tuyệt vời, BTW.
martineau

25

Đọc tệp nhị phân trong Python và lặp qua từng byte

Điểm mới trong Python 3.5 là pathlibmô-đun, có một phương thức tiện lợi đặc biệt để đọc trong tệp dưới dạng byte, cho phép chúng ta lặp lại qua các byte. Tôi coi đây là một câu trả lời đàng hoàng (nếu nhanh và bẩn):

import pathlib

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

Thật thú vị khi đây là câu trả lời duy nhất để đề cập đến pathlib .

Trong Python 2, có lẽ bạn sẽ làm điều này (như Vinay Sajip cũng gợi ý):

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

Trong trường hợp tệp có thể quá lớn để lặp lại trong bộ nhớ, bạn sẽ chunk nó, một cách tự nhiên, sử dụng iterhàm có callable, sentinelchữ ký - phiên bản 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)

(Một số câu trả lời khác đề cập đến điều này, nhưng một số ít cung cấp kích thước đọc hợp lý.)

Thực hành tốt nhất cho các tệp lớn hoặc đọc đệm / tương tác

Hãy tạo một hàm để làm điều này, bao gồm cả việc sử dụng thành ngữ của thư viện chuẩn cho 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

Lưu ý rằng chúng tôi sử dụng file.read1. file.readchặn cho đến khi nó nhận được tất cả các byte được yêu cầu của nó hoặc EOF.file.read1cho phép chúng tôi tránh bị chặn và nó có thể quay lại nhanh hơn vì điều này. Không có câu trả lời khác đề cập đến điều này là tốt.

Trình diễn cách sử dụng thực hành tốt nhất:

Chúng ta hãy tạo một tệp với một megabyte (thực sự là mebibyte) của dữ liệu giả ngẫu nhiên:

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

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

Bây giờ chúng ta hãy lặp lại nó và cụ thể hóa nó trong bộ nhớ:

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

Chúng tôi có thể kiểm tra bất kỳ phần nào của dữ liệu, ví dụ: 100 byte cuối cùng và 100 byte đầu tiên:

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

Không lặp lại theo dòng cho các tệp nhị phân

Đừng làm như sau - điều này kéo một đoạn có kích thước tùy ý cho đến khi nó chuyển sang ký tự dòng mới - quá chậm khi các khối quá nhỏ và cũng có thể quá lớn:

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

Ở trên chỉ tốt cho các tệp văn bản có thể đọc được về mặt ngữ nghĩa của con người (như văn bản đơn giản, mã, đánh dấu, đánh dấu, v.v ... về cơ bản là bất cứ thứ gì được mã hóa, utf, latin, v.v.) mà bạn nên mở mà không cần 'b'cờ.


2
Điều này tốt hơn nhiều ... cảm ơn bạn đã làm điều này. Tôi biết không phải lúc nào cũng vui khi quay lại câu trả lời hai năm tuổi, nhưng tôi đánh giá cao việc bạn đã làm nó. Tôi đặc biệt thích phân nhóm "Đừng lặp lại theo dòng" :-)
Floris

1
Xin chào Aaron, có lý do nào khiến bạn chọn sử dụng path = Path(path), with path.open('rb') as file:thay vì sử dụng chức năng mở tích hợp thay thế không? Cả hai cùng làm điều đúng?
Joshua Yonathan

1
@JoshuaYonathan Tôi sử dụng Pathđối tượng vì đó là một cách mới rất thuận tiện để xử lý các đường dẫn. Thay vì chuyển một chuỗi vào các hàm "đúng" được chọn cẩn thận, chúng ta chỉ cần gọi các phương thức trên đối tượng đường dẫn, về cơ bản chứa hầu hết các chức năng quan trọng mà bạn muốn với chuỗi đường dẫn. Với các IDE có thể kiểm tra, chúng ta cũng có thể dễ dàng tự động hoàn thành hơn. Chúng ta có thể thực hiện tương tự với opennội trang, nhưng có rất nhiều mặt tích cực khi viết chương trình cho lập trình viên sử dụng Pathđối tượng thay thế.
Aaron Hall

1
Phương pháp cuối cùng bạn đề cập bằng cách sử dụng hàm, file_byte_iteratornhanh hơn nhiều so với tất cả các phương pháp tôi đã thử trên trang này. Thanh danh cho bạn!
Rick M.

@RickM: Bạn có thể quan tâm đến điểm chuẩn tôi vừa đăng.
martineau

19

Để tổng hợp tất cả các điểm tuyệt vời của chrispy, Skurmedel, Ben Hoyt và Peter Hansen, đây sẽ là giải pháp tối ưu để xử lý tệp nhị phân một byte mỗi lần:

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

Đối với phiên bản python 2.6 trở lên, bởi vì:

  • bộ đệm python trong nội bộ - không cần phải đọc khối
  • Nguyên tắc DRY - không lặp lại dòng đọc
  • với tuyên bố đảm bảo đóng tệp sạch
  • 'byte' ước tính thành false khi không còn byte nữa (không phải khi byte bằng 0)

Hoặc sử dụng giải pháp JF Sebastians để cải thiện tốc độ

from functools import partial

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

Hoặc nếu bạn muốn nó là một hàm tạo như được trình bày bởi 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
Như câu trả lời được liên kết nói, việc đọc / xử lý một byte tại một thời điểm vẫn chậm trong Python ngay cả khi các lần đọc được đệm. Hiệu suất có thể được cải thiện mạnh mẽ nếu một vài byte tại một thời điểm có thể được xử lý như trong ví dụ trong câu trả lời được liên kết: 1,5 GB / s so với 7,5 MB / s.
jfs

6

Python 3, đọc tất cả các tệp cùng một lúc:

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

Bạn có thể lặp lại bất cứ điều gì bạn muốn bằng cách sử dụng databiến.


6

Sau khi thử tất cả các câu hỏi trên và sử dụng câu trả lời từ @Aaron Hall, tôi đã gặp lỗi bộ nhớ cho tệp ~ 90 Mb trên máy tính chạy Window 10, RAM 8 Gb và Python 3.5 32 bit. Tôi đã được một đồng nghiệp giới thiệu để sử dụng numpythay thế và nó hoạt động kỳ diệu.

Cho đến nay, nhanh nhất để đọc toàn bộ tệp nhị phân (mà tôi đã kiểm tra) là:

import numpy as np

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

Tài liệu tham khảo

Đa số nhanh hơn bất kỳ phương pháp nào cho đến nay. Hy vọng nó sẽ giúp được ai đó!


3
Đẹp, nhưng không thể được sử dụng trên tệp nhị phân chứa các loại dữ liệu khác nhau.
Nertal

@Nirmal: Câu hỏi là về việc lặp qua byte đạt được, vì vậy không rõ liệu nhận xét của bạn về các loại dữ liệu khác nhau có liên quan hay không.
martineau

1
Rick: Mã của bạn không hoạt động tương tự như các mã khác - cụ thể là lặp qua từng byte. Nếu được thêm vào nó, nó sẽ không nhanh hơn phần lớn những người khác theo ít nhất là theo kết quả trong điểm chuẩn của tôi . Trong thực tế, nó dường như là một trong những cách tiếp cận chậm hơn. Nếu việc xử lý được thực hiện cho từng byte (bất cứ điều gì có thể) là điều có thể được thực hiện thông qua numpy, thì có thể đáng giá.
martineau

@martineau Cảm ơn ý kiến ​​của bạn, vâng tôi hiểu rằng câu hỏi là về việc lặp qua từng byte và không chỉ tải mọi thứ trong một lần, mà còn có những câu trả lời khác trong câu hỏi này cũng chỉ ra cách đọc tất cả nội dung và do đó câu trả lời của tôi
Rick M.

4

Nếu bạn có nhiều dữ liệu nhị phân để đọc, bạn có thể muốn xem xét mô-đun struct . Nó được ghi nhận là chuyển đổi "giữa các loại C và Python", nhưng tất nhiên, byte là byte và việc chúng được tạo như loại C không thành vấn đề. Ví dụ: nếu dữ liệu nhị phân của bạn chứa hai số nguyên 2 byte và một số nguyên 4 byte, bạn có thể đọc chúng như sau (ví dụ được lấy từ structtài liệu):

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

Bạn có thể thấy điều này thuận tiện hơn, nhanh hơn hoặc cả hai, hơn là lặp lại rõ ràng nội dung của một tệp.


4

Bài viết này không phải là một câu trả lời trực tiếp cho câu hỏi. Thay vào đó, điểm chuẩn mở rộng dựa trên dữ liệu có thể được sử dụng để so sánh nhiều câu trả lời (và các biến thể sử dụng các tính năng mới được thêm vào sau này, các phiên bản Python hiện đại hơn) đã được đăng vào câu hỏi này - và do đó nên có ích trong việc xác định cái nào có hiệu suất tốt nhất.

Trong một vài trường hợp, tôi đã sửa đổi mã trong câu trả lời được tham chiếu để làm cho nó tương thích với khung chuẩn.

Đầu tiên, đây là kết quả cho những gì hiện tại là phiên bản mới nhất của 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

Tôi cũng đã chạy nó với tệp thử nghiệm 10 MiB lớn hơn nhiều (mất gần một giờ để chạy) và nhận được kết quả hiệu suất tương đương với các tệp được hiển thị ở trên.

Đây là mã được sử dụng để làm điểm chuẩn:

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

Bạn đang giả sử tôi làm yield from chunkthay thế for byte in chunk: yield byte? Tôi nghĩ tôi nên thắt chặt câu trả lời của mình với điều đó.
Aaron Hall

@Aaron: Có hai phiên bản câu trả lời của bạn trong kết quả Python 3 và một trong số đó sử dụng yield from.
martineau

ok, tôi đã cập nhật câu trả lời của tôi. Ngoài ra, tôi khuyên bạn nên bỏ đi enumeratevì việc lặp lại nên được hiểu là hoàn thành - nếu không, tôi đã kiểm tra lần cuối - liệt kê có một chút chi phí so với việc thực hiện sổ sách kế toán với + = 1, vì vậy bạn có thể thay thế việc ghi sổ trong mã riêng. Hoặc thậm chí vượt qua một deque với maxlen=0.
Aaron Hall

@Aaron: Đồng ý về enumerate. Cảm ơn vì bạn đã phản hồi. Sẽ thêm một bản cập nhật cho bài viết của tôi mà không có nó (mặc dù tôi không nghĩ rằng nó thay đổi kết quả nhiều). Cũng sẽ thêm numpycâu trả lời dựa trên @Rick M.
martineau

Xem xét thêm một chút về mã: Tôi không nghĩ sẽ có ý nghĩa gì khi viết câu trả lời cho Python 2 vào thời điểm này - Tôi sẽ xem xét loại bỏ Python 2 vì tôi mong bạn sử dụng Python 64 hoặc 3,8 bit. Bạn có thể thiết lập dọn dẹp ở cuối cùng với atexit và một phần ứng dụng. Typo: "xác minh". Tôi thấy không có ý nghĩa trong việc sao chép các chuỗi thử nghiệm - chúng có khác nhau không? Tôi tưởng tượng nếu bạn sử dụng super().thay vì tuple.trong __new__bạn có thể sử dụng namedtupletên thuộc tính thay vì chỉ mục.
Aaron Hall

3

nếu bạn đang tìm kiếm thứ gì đó nhanh chóng, thì đây là một phương pháp tôi đã sử dụng trong nhiều năm qua:

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)

nếu bạn muốn lặp lại ký tự thay vì ints, bạn có thể chỉ cần sử dụng data = file.read(), đó phải là một đối tượng byte () trong py3.


1
'mảng' được nhập bởi 'từ mảng nhập mảng'
quanly_mc

@quanly_mc vâng, cảm ơn vì đã nắm bắt được điều đó, và xin lỗi tôi đã quên bao gồm điều đó, chỉnh sửa ngay bây giờ.
Tcll
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.