İkili dosyayı okuma ve her bayt üzerinde döngü oluşturma


377

Python'da, bir ikili dosyada nasıl okurum ve o dosyanın her baytı üzerinde nasıl döngü yapabilirim?

Yanıtlar:


387

Python 2.4 ve Daha Önceki

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)

With ifadesinin 2.5'in altındaki Python sürümlerinde mevcut olmadığını unutmayın. V 2.5 sürümünde kullanmak için içe aktarmanız gerekir:

from __future__ import with_statement

2.6'da buna gerek yoktur.

Python 3

Python 3'te biraz farklı. Bayt modunda artık bayt modunda ham karakterleri almayacağız, ancak bayt nesnelerini alacağız, bu nedenle koşulu değiştirmemiz gerekiyor:

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

Ya da benhoyt'ın dediği gibi, eşit olmayanı atlayın ve b""yanlış olarak değerlendirilen gerçeğinden yararlanın . Bu, kodu 2.6 ve 3.x arasında herhangi bir değişiklik yapmadan uyumlu hale getirir. Ayrıca bayt modundan metne veya tersine geçerseniz durumu değiştirmenize de engel olur.

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

piton 3.8

Şu andan itibaren sayesinde: = operatör yukarıdaki kod daha kısa bir şekilde yazılabilir.

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

40
Bayt-bir dosya okumak bir performans kabusu. Bu, python'da mevcut en iyi çözüm olamaz. Bu kod dikkatli kullanılmalıdır.
usr

7
@usr: Dosya nesneleri dahili olarak arabelleğe alınır ve buna rağmen istenen şey budur. Her komut dosyasının en iyi performansa ihtiyacı yoktur.
Skurmedel

4
@mezhaka: Böylece okumak (1) 'den okumak (bufsize)' e değiştirirsiniz ve while döngüsü içinde bir for-in yaparsınız ... örnek hala duruyor.
Skurmedel

3
@usr: performans farkı denediğim kod için 200 kat daha fazla olabilir .
jfs

2
@usr - kaç bayt işlemek istediğinize bağlıdır. Yeterince azsa, "kötü" performans gösteren, ancak kolayca anlaşılabilir kod çok tercih edilebilir. CPU döngülerinin israfı, kodu korurken "okuyucu CPU döngülerini" kaydetmek için telafi edilir.
IllvilJa

172

Bu jeneratör, bir dosyadan bayt verir ve dosyayı parçalar halinde okur:

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)

Yineleyiciler ve jeneratörler hakkında bilgi için Python belgelerine bakın .


3
@codeape Tam aradığım şey. Ancak, parça boyutunu nasıl belirlersiniz? Keyfi bir değer olabilir mi?
swdev

3
@swdev: Örnek 8192 bir ChunkSize kullanır bayt . File.read () - fonksiyonunun parametresi sadece boyutu, yani okunacak Bayt sayısını belirtir. codeape seçti 8192 Byte = 8 kB(aslında öyle KiBama yaygın olarak bilinmeyen). Değer "tamamen" rastgele ancak 8 kB uygun bir değer gibi görünüyor: çok fazla bellek boşa gitmiyor ve hala Skurmedel'in kabul ettiği cevapta olduğu gibi "çok fazla" okuma işlemi yok ...
mozzbozz

3
Dosya sistemi zaten veri yığınlarını arabelleğe alır, bu nedenle bu kod gereksizdir. Bir seferde bir bayt okumak daha iyidir.
sade

17
Kabul edilen cevaptan zaten daha hızlı olsa da, bu, en içteki tüm for b in chunk:döngüyü değiştirerek% 20-25 oranında daha hızlı olabilir yield from chunk. Bu form yieldPython 3.3'e eklenmiştir (bakınız Verim İfadeleri ).
martineau

3
Hmm pek olası görünmüyor, bağlantı?
codeape

54

Eğer dosya hafızada tutmayacak kadar büyük değilse sorun olur:

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

Burada süreç_baytı, iletilen bayt üzerinde gerçekleştirmek istediğiniz bir işlemi temsil eder.

Bir kerede bir yığın işlemek istiyorsanız:

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)

Bu withifade Python 2.5 ve daha ileri sürümlerde mevcuttur.


1
Az önce gönderdiğim karşılaştırmayla ilgileniyor olabilirsiniz .
martineau

37

Bir dosyayı okumak için - her seferinde bir bayt (arabelleğe alma yoksayılıyor) - iki bağımsız değişkenli iter(callable, sentinel)yerleşik işlevi kullanabilirsiniz :

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

file.read(1)Hiçbir şey geri dönmeyene kadar çağırır b''(boş bytestring). Bellek büyük dosyalar için sınırsız büyümez. Sen geçebileceği buffering=0 kadar open()belleğe alma devre dışı bırakmak için, - bu sadece bir byte yineleme (yavaş) başına okunan olduğunu garanti eder.

with-statement dosyayı otomatik olarak kapatır - altındaki kodun bir istisna oluşturması durumu dahil.

Varsayılan olarak dahili tamponlamanın varlığına rağmen, her seferinde bir bayt işlemek hala yetersizdir. Örneğin, burada blackhole.pyverilen her şeyi yiyen yardımcı program:

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

Misal:

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

Bu işler ~ 1,5 GB / s zaman chunksize == 32768benim makine ve yalnızca ~ 7.5 MB / s iken chunksize == 1. Yani, bir seferde bir bayt okumak 200 kat daha yavaştır. Bir kerede birden fazla bayt kullanmak için işleminizi yeniden yazabiliyorsanız ve performansa ihtiyacınız varsa bunu dikkate alın.

mmapbytearraybir dosyayı aynı anda bir ve bir nesne nesnesi olarak görmenizi sağlar . Her iki arabirime de erişmeniz gerekiyorsa, dosyanın tamamını belleğe yüklemeye alternatif olarak kullanılabilir. Özellikle, yalnızca düz bir fordöngü kullanarak bellek eşlemeli bir dosya üzerinde bir kerede bir bayt yineleyebilirsiniz :

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

mmapdilim gösterimini destekler. Örneğin, konumdan başlayarak dosyadan bayt mm[i:i+len]döndürür . Bağlam yöneticisi protokolü Python 3.2'den önce desteklenmez; bu durumda açıkça arama yapmanız gerekir . Kullanarak her bayt üzerinde yineleme yapmak daha fazla bellek tüketir , ancak daha hızlı bir sıralamadır.lenimm.close()mmapfile.read(1)mmap


Son örneği çok ilginç buldum. Çok kötü eşdeğer numpybellek eşlemeli (bayt) diziler yok.
martineau

1
@martineau var numpy.memmap()ve her seferinde bir bayt veri alabilirsiniz (ctypes.data). Numpy dizileri bellek + meta verilerdeki lekelerden biraz daha fazlası olarak düşünebilirsiniz.
jfs

jfs: Teşekkürler, mükemmel haberler! Böyle bir şey bilmiyordum. Harika cevap, BTW.
martineau

25

Python'daki ikili dosyayı okuma ve her bayt üzerinde döngü oluşturma

Python 3.5'teki yenilikler pathlib olan, özellikle bir dosyada bayt olarak okumak ve baytlar üzerinde yineleme yapmamıza izin veren bir kolaylık yöntemine sahip modüldür. Ben bu (hızlı ve kirli ise) iyi bir cevap olarak düşünün:

import pathlib

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

İlginçtir ki tek cevap budur pathlib.

Python 2'de muhtemelen bunu yaparsınız (Vinay Sajip'in de belirttiği gibi):

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

Dosyanın bellekte yinelemek için çok büyük olması durumunda iter, callable, sentinelimzalı işlevi (Python 2 sürümü) kullanarak deyimsel olarak parçalara ayırırsınız :

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)

(Diğer bazı cevaplar bundan bahseder, ancak çok azı mantıklı bir okuma boyutu sunar.)

Büyük dosyalar veya arabelleğe alınmış / etkileşimli okuma için en iyi uygulama

Bunu yapmak için, Python 3.5+ için standart kitaplığın deyimsel kullanımları da dahil olmak üzere bir işlev oluşturalım:

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

Kullandığımızı unutmayın file.read1. file.readistenen tüm baytları alana kadar engeller veya EOF.file.read1engellemekten kaçınmamızı sağlar ve bu nedenle daha hızlı geri dönebilir. Başka cevap da bundan bahsetmiyor.

En iyi uygulama kullanımının gösterilmesi:

Bir megabayt (aslında mebibyte) sahte veri içeren bir dosya yapalım:

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

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

Şimdi onu tekrarlayalım ve hafızada gerçekleştirelim:

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

Verilerin herhangi bir bölümünü, örneğin son 100 ve ilk 100 baytı inceleyebiliriz:

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

İkili dosyalar için satırlara göre yineleme yapma

Aşağıdakileri yapmayın - bu, yeni satır karakterine ulaşıncaya kadar keyfi boyutta bir yığın çeker - parçalar çok küçük ve muhtemelen çok büyük olduğunda çok yavaş:

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

Yukarıdaki sadece semantik olarak okunabilir metin dosyaları (düz metin, kod, biçimlendirme, işaretleme vb ... aslında ascii, utf, latin, vb ... kodlanmış gibi) 'b'bayrağı olmadan açmanız gereken şeyler için iyidir .


2
Bu çok daha iyi ... bunu yaptığınız için teşekkür ederim. İki yıllık bir cevaba geri dönmek her zaman eğlenceli olmadığını biliyorum, ama bunu yaptığınızı takdir ediyorum. Özellikle "satırları yineleme yok" alt başlığını seviyorum :-)
Floris

1
Merhaba Aaron, path = Path(path), with path.open('rb') as file:bunun yerine yerleşik açık işlevi kullanmak yerine kullanmayı seçmenizin bir nedeni var mı? İkisi de aynı şeyi doğru mu yapıyor?
Joshua Yonathan

1
@JoshuaYonathan PathNesneyi kullanıyorum çünkü yolları işlemek için çok uygun yeni bir yol. Bir dizenin etrafını dikkatle seçilmiş "doğru" işlevlere geçirmek yerine, yol nesnesinde, anlamsal olarak bir yol dizgisi ile istediğiniz önemli işlevselliğin çoğunu içeren yöntemleri çağırabiliriz. İnceleyebilen IDE'ler ile otomatik tamamlamayı da daha kolay elde edebiliriz. openYerleşik ile aynı şeyi başarabiliriz , ancak programı yazarken programcının Pathnesneyi kullanması için çok sayıda yukarı yönlü hareket vardır .
Aaron Hall

1
Bu işlevi kullanarak bahsettiğiniz son yöntem file_byte_iterator, bu sayfada denediğim tüm yöntemlerden çok daha hızlıdır. Şeref!
Rick

@RickM: Az önce gönderdiğim karşılaştırmayla ilgileniyor olabilirsiniz .
martineau

19

Chrispy, Skurmedel, Ben Hoyt ve Peter Hansen'in tüm parlak noktalarını özetlemek için, ikili dosyayı her seferinde bir bayt işlemek için en uygun çözüm olacaktır:

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

Python 2.6 ve üzeri sürümler için:

  • python dahili olarak tamponlar - yığınları okumaya gerek yok
  • NEM ALMA prensibi - okuma satırını tekrarlamayın
  • ifadesi ile temiz bir dosya kapatılmasını sağlar
  • 'bayt' artık bayt olmadığında yanlış olarak değerlendirilir (bayt sıfır olduğunda değil)

Veya daha yüksek hız için JF Sebastians çözümünü kullanın

from functools import partial

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

Veya codeape tarafından gösterilen gibi bir jeneratör işlevi olarak istiyorsanız:

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
Bağlantılı cevabın dediği gibi, okumalar arabelleğe alınmış olsa bile Python'da bir seferde bir bayt okuma / işleme hala yavaştır. Bağlantılı yanıttaki örnekteki gibi bir seferde birkaç bayt işlenebiliyorsa, performans önemli ölçüde geliştirilebilir: 1,5 GB / sn ve 7,5 MB / sn.
jfs

6

Python 3, tüm dosyayı bir kerede okuyun:

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

dataDeğişkeni kullanarak istediğinizi yineleyebilirsiniz .


6

Yukarıdakilerin tümünü denedikten ve @Aaron Hall'dan gelen cevabı kullandıktan sonra, Windows 10, 8 Gb RAM ve Python 3.5 32 bit çalıştıran bir bilgisayarda ~ 90 Mb'lik bir dosya için bellek hataları alıyordum. Bir meslektaşım tarafından kullanılması önerildinumpy yerine ve harikalar yaratıyor.

Şimdiye kadar, (test ettik) tüm bir ikili dosyayı okumak için en hızlı:

import numpy as np

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

Referans

Şimdiye kadar diğer yöntemlere göre çok daha hızlıdır. Umarım birine yardım eder!


3
Güzel, ancak farklı veri türleri içeren ikili dosyalarda kullanılamaz.
Nirmal

@Nirmal: Soru erişim baytında döngü ile ilgili, bu nedenle farklı veri türleri hakkındaki yorumunuzun herhangi bir etkisi olup olmadığı net değil.
martineau

1
Rick: Kodunuz diğerleriyle aynı şeyi yapmıyor - yani her bir bayt üzerinde döngü yapıyor. Buna eklenirse, en azından karşılaştırmamdaki sonuçlara göre diğerlerinin çoğundan daha hızlı değildir . Aslında daha yavaş yaklaşımlardan biri gibi görünüyor. Her bayta yapılan işlem (her ne olursa olsun), yapılabilecek bir şeyse numpy, o zaman faydalı olabilir.
martineau

@martineau Yorumlarınız için teşekkürler, evet sorunun her bayt üzerinde döngü ve her şeyi bir seferde yüklemekle ilgili olmadığını anlıyorum, ancak bu soruda tüm içeriği okumaya ve dolayısıyla cevabımı da gösteren başka cevaplar var
Rick M.

4

Okunacak çok fazla ikili veri varsa, struct modülünü düşünmek isteyebilirsiniz . "C ve Python türleri arasında dönüştürme" olarak belgelenmiştir, fakat elbette bayt bayttır ve bunların C tipi olarak yaratılıp yaratılmalarının önemi yoktur. Örneğin, ikili verileriniz iki adet 2 baytlık tamsayı ve bir adet 4 baytlık tamsayı içeriyorsa, bunları aşağıdaki gibi okuyabilirsiniz (örnek structbelgelerden alınmıştır ):

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

Bunu, bir dosyanın içeriğini açıkça belirtmekten daha uygun, daha hızlı veya her ikisini de bulabilirsiniz.


4

Bu yazının kendisi soruya doğrudan bir cevap değildir. Bunun yerine, bu soruya gönderilen cevapların çoğunu (ve daha sonraki, daha modern Python sürümlerinde eklenen yeni özelliklerin kullanılmasının çeşitlerini) karşılaştırmak için kullanılabilecek veri odaklı genişletilebilir bir karşılaştırma ölçütüdür - ve bu nedenle hangisinin en iyi performansa sahip olduğuna karar vermede yardımcı olun.

Bazı durumlarda, referans çerçevedeki kodu, karşılaştırma çerçevesiyle uyumlu hale getirmek için değiştirdim.

İlk olarak, şu anda Python 2 & 3'ün en son sürümlerinin sonuçları:

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

Ayrıca çok daha büyük bir 10 MiB test dosyasıyla (çalıştırılması yaklaşık bir saat sürdü) çalıştırdım ve yukarıda gösterilenlerle karşılaştırılabilir performans sonuçları aldım.

Kıyaslama yapmak için kullanılan kod:

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

Onun yield from chunkyerine benim yaptığımı mı sanıyorsun for byte in chunk: yield byte? Cevabımı bununla sıkmam gerektiğini düşünüyorum.
Aaron Hall

@Aaron: Python 3 sonuçlarında cevabınızın iki versiyonu var ve bunlardan biri kullanıyor yield from.
martineau

tamam, cevabımı güncelledim. Ayrıca enumerateyinelemenin tamamlandığı anlaşılacağı için düşmenizi öneririm - eğer değilse, son kontrol ettim - numaralandırmanın + = 1 ile endeks için defter tutma üzerinde maliyetlerle biraz ek yük var, bu yüzden alternatif olarak kendi kodu. Ya da ile bir deque'ye geç maxlen=0.
Aaron Hall

@Aaron: Bu konuda hemfikirim enumerate. Geri dönüşünüz için teşekkür ederiz. Yayınıma sahip olmayan bir güncelleme ekleyeceğim (sonuçları çok değiştirdiğini düşünmeme rağmen). Ayrıca @Rick M.'nin numpytabanlı cevabını da ekleyeceğiz .
martineau

Biraz daha kod incelemesi: Bu noktada Python 2'ye cevap yazmanın herhangi bir anlamı olduğunu düşünmüyorum - 64 bit Python 3.7 veya 3.8 kullanmanızı beklediğiniz gibi Python 2'yi kaldırmayı düşünürüm. Temizleme işlemini, atexit ve kısmi bir uygulama ile sonuna kadar ayarlayabilirsiniz. Yazım hatası: "doğrula". Test dizelerinin çoğaltılmasında bir anlam görmüyorum - hepsi farklı mı? Eğer kullanırsanız hayal super().yerine tuple.senin içinde __new__sen kullanabilirsiniz namedtupleyerine indeksler özellik adlarını.
Aaron Hall

3

hızlı bir şey arıyorsanız, işte yıllardır çalıştığım bir yöntem:

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)

ints yerine karakterleri yinelemek istiyorsanız data = file.read(), py3 içinde bir bayt () nesnesi olması gereken basitçe kullanabilirsiniz .


1
'dizi', 'dizi içe aktarma dizisinden' tarafından içe aktarılır
quanly_mc

@ quanly_mc evet, bunu yakaladığınız için teşekkürler ve üzgünüm, şimdi düzenlemeyi de dahil etmeyi unuttum.
Tcll
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.