W Pythonie, jak mam czytać plik binarny i zapętlać każdy bajt tego pliku?
377
Python 2.4 i wcześniejsze
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)
Zauważ, że instrukcja with nie jest dostępna w wersjach Pythona poniżej 2.5. Aby użyć go w wersji 2.5, musisz go zaimportować:
from __future__ import with_statement
W 2.6 nie jest to potrzebne.
Python 3
W Pythonie 3 jest nieco inaczej. Nie będziemy już pobierać nieprzetworzonych znaków ze strumienia w trybie bajtowym, ale obiekty bajtowe, dlatego musimy zmienić warunek:
with open("myfile", "rb") as f:
byte = f.read(1)
while byte != b"":
# Do stuff with byte.
byte = f.read(1)
Lub, jak mówi Benhoyt, pomiń nierównomierne i skorzystaj z faktu, że b""
wartość jest fałszywa. Dzięki temu kod jest zgodny między wersjami 2.6 a 3.x bez żadnych zmian. Oszczędziłoby to również zmiany warunku, jeśli przejdziesz z trybu bajtowego na tekstowy lub odwrotnie.
with open("myfile", "rb") as f:
byte = f.read(1)
while byte:
# Do stuff with byte.
byte = f.read(1)
python 3.8
Od teraz dzięki: = operator powyższy kod można napisać w krótszy sposób.
with open("myfile", "rb") as f:
while (byte := f.read(1)):
# Do stuff with byte.
Ten generator generuje bajty z pliku, odczytując go w porcjach:
Informacje na temat iteratorów i generatorów można znaleźć w dokumentacji Pythona .
źródło
8192 Byte = 8 kB
(właściwie to,KiB
ale to nie jest tak powszechnie znane). Wartością jest „całkowicie” random ale 8 kB wydaje się być odpowiednia wartość: nie zbyt dużo pamięci jest marnowana i nadal nie są „zbyt wiele” operacji odczytu jak w przyjętym Odpowiedź Skurmedel ...for b in chunk:
pętlęyield from chunk
. Ta formayield
została dodana w Pythonie 3.3 (patrz Wyrażenia wydajności ).Jeśli plik nie jest zbyt duży, problemem jest trzymanie go w pamięci:
gdzie bajt_procesu reprezentuje operację, którą chcesz wykonać na przekazanym bajcie.
Jeśli chcesz przetwarzać porcję na raz:
with
Stwierdzenie jest dostępny w Pythonie 2.5 i większej.źródło
Aby odczytać plik - jeden bajt na raz (ignorując buforowanie) - możesz użyć wbudowanej funkcji dwóch argumentów
iter(callable, sentinel)
:Wywołuje,
file.read(1)
dopóki nic nie zwrócib''
(puste bajtowanie). Pamięć nie rośnie bez ograniczeń dla dużych plików. Możesz przejśćbuffering=0
doopen()
, aby wyłączyć buforowanie - gwarantuje to, że tylko jeden bajt jest odczytywany na jedną iterację (powoli).with
-statement zamyka plik automatycznie - w tym przypadek, gdy kod poniżej generuje wyjątek.Mimo obecności domyślnego buforowania wewnętrznego przetwarzanie jednego bajtu na raz nadal jest nieefektywne. Na przykład, oto
blackhole.py
narzędzie, które zjada wszystko, co zostało podane:Przykład:
Przetwarza ~ 1,5 GB / s, gdy
chunksize == 32768
na moim komputerze i tylko ~ 7,5 MB / s, kiedychunksize == 1
. Oznacza to, że czytanie bajtu na raz jest 200 razy wolniejsze. Weź to pod uwagę, jeśli możesz przepisać swoje przetwarzanie, aby używać więcej niż jednego bajtu na raz i jeśli potrzebujesz wydajności.mmap
pozwala traktować plik jednocześnie jakobytearray
obiekt pliku. Może służyć jako alternatywa dla ładowania całego pliku do pamięci, jeśli potrzebujesz dostępu do obu interfejsów. W szczególności możesz iterować jeden bajt naraz po pliku odwzorowanym w pamięci, używając zwykłejfor
pętli:mmap
obsługuje notację plastra. Na przykładmm[i:i+len]
zwracalen
bajty z pliku zaczynając od pozycjii
. Protokół menedżera kontekstu nie jest obsługiwany przed Pythonem 3.2;mm.close()
w takim przypadku musisz zadzwonić jawnie. Iteracja po każdym bajciemmap
zużywa więcej pamięci niżfile.read(1)
, alemmap
jest o rząd wielkości szybsza.źródło
numpy
tablic odwzorowanych w pamięci (bajtowych).numpy.memmap()
i możesz pobrać dane jeden bajt na raz (ctypes.data). Mógłbyś myśleć o tablicach numpy jako o czymś więcej niż o obiektach blob w pamięci + metadanych.Nowością w Pythonie 3.5 jest
pathlib
moduł, który ma wygodną metodę wczytywania pliku jako bajtów, co pozwala nam na iterację po bajtach. Uważam to za przyzwoitą (choć szybką i brudną) odpowiedź:Ciekawe, że to jedyna wspomniana odpowiedź
pathlib
.W Pythonie 2 prawdopodobnie zrobiłbyś to (jak sugeruje również Vinay Sajip):
W przypadku, gdy plik może być zbyt duży, aby iterować w pamięci, można go porcjować idiomatycznie, używając
iter
funkcji zcallable, sentinel
podpisem - wersja Python 2:(Kilka innych odpowiedzi wspomina o tym, ale niewiele z nich oferuje rozsądny rozmiar odczytu.)
Najlepsza praktyka w przypadku dużych plików lub odczytu buforowanego / interaktywnego
Utwórzmy w tym celu funkcję, w tym idiomatyczne zastosowania standardowej biblioteki dla Python 3.5+:
Pamiętaj, że korzystamy
file.read1
.file.read
blokuje, dopóki nie otrzyma wszystkich wymaganych bajtów lubEOF
.file.read1
pozwala nam uniknąć blokowania i dzięki temu może szybciej wrócić. Żadne inne odpowiedzi również o tym nie wspominają.Pokaz stosowania najlepszych praktyk:
Stwórzmy plik z megabajtem (właściwie mebibajtem) danych pseudolosowych:
Teraz powtórzmy to i zmaterializujmy w pamięci:
Możemy sprawdzić dowolną część danych, na przykład ostatnie 100 i pierwsze 100 bajtów:
Nie iteruj według wierszy dla plików binarnych
Nie wykonuj następujących czynności - spowoduje to wyciągnięcie kawałka o dowolnym rozmiarze, aż dojdzie do znaku nowej linii - zbyt wolno, gdy fragmenty są zbyt małe, a być może również zbyt duże:
Powyższe jest dobre tylko dla tego, co jest semantycznie czytelnym dla człowieka plikiem tekstowym (takim jak zwykły tekst, kod, znaczniki, markdown itp.) Zasadniczo wszystko, co jest zakodowane w ascii, utf, latin itp.), Które powinieneś otworzyć bez
'b'
flagi.źródło
path = Path(path), with path.open('rb') as file:
zamiast wbudowanej funkcji otwartej? Oboje robią to samo, prawda?Path
obiektu, ponieważ jest to bardzo wygodny nowy sposób obsługi ścieżek. Zamiast przekazywać ciąg znaków do starannie wybranych „właściwych” funkcji, możemy po prostu wywołać metody na obiekcie ścieżki, który zasadniczo zawiera większość ważnych funkcji, których potrzebujesz z semantycznie ciągiem ścieżki. Dzięki IDE, które mogą kontrolować, łatwiej możemy również uzyskać autouzupełnianie. To samo możemy osiągnąć dziękiopen
wbudowanemu programowi, ale przy pisaniu programu programiści mogą korzystać zPath
tego obiektu.file_byte_iterator
jest znacznie szybsza niż wszystkie metody, które wypróbowałem na tej stronie. Uznanie dla ciebie!Podsumowując wszystkie genialne punkty chrispy, Skurmedel, Ben Hoyt i Peter Hansen, byłoby to optymalne rozwiązanie do przetwarzania pliku binarnego po jednym bajcie:
Dla wersji Python 2.6 i nowszych, ponieważ:
Lub użyj rozwiązania JF Sebastians dla zwiększenia prędkości
Lub jeśli chcesz to jako funkcję generatora, jak pokazano w codeape:
źródło
Python 3, przeczytaj cały plik na raz:
Możesz iterować dowolnie, używając
data
zmiennej.źródło
Po wypróbowaniu wszystkich powyższych informacji i użyciu odpowiedzi z @Aaron Hall otrzymywałem błędy pamięci dla pliku o wielkości ~ 90 Mb na komputerze z systemem Windows 10, 8 Gb RAM i 32-bitowym Python 3.5. Polecił mi kolega z pracy
numpy
Zamiast tego i działa to cuda.Zdecydowanie najszybszy odczyt całego pliku binarnego (który przetestowałem) to:
Odniesienie
Mnóstwo szybciej niż jakiekolwiek inne metody do tej pory. Mam nadzieję, że to komuś pomaga!
źródło
numpy
, to może być opłacalne.Jeśli masz dużo danych binarnych do odczytania, możesz rozważyć moduł struct . Jest to udokumentowane jako konwersja „między typami C i Python”, ale oczywiście bajty są bajtami i to, czy zostały utworzone jako typy C, nie ma znaczenia. Na przykład, jeśli dane binarne zawierają dwie 2-bajtowe liczby całkowite i jedną 4-bajtową liczbę całkowitą, możesz je odczytać w następujący sposób (przykład wzięty z
struct
dokumentacji):Może się to okazać wygodniejsze, szybsze lub jedno i drugie, niż jawne zapętlanie zawartości pliku.
źródło
Sam post nie jest bezpośrednią odpowiedzią na pytanie. Zamiast tego jest oparty na danych, rozszerzalny test porównawczy, którego można użyć do porównania wielu odpowiedzi (i wariantów wykorzystania nowych funkcji dodanych w późniejszych, bardziej nowoczesnych wersjach Pythona), które zostały opublikowane na to pytanie - i dlatego powinny być pomocnym w określeniu, która ma najlepszą wydajność.
W kilku przypadkach zmodyfikowałem kod w cytowanej odpowiedzi, aby był zgodny z frameworkiem testu porównawczego.
Po pierwsze, oto wyniki dla najnowszych wersji Python 2 i 3:
Uruchomiłem go również ze znacznie większym plikiem testowym 10 MiB (którego uruchomienie zajęło prawie godzinę) i uzyskałem wyniki wydajności porównywalne do pokazanych powyżej.
Oto kod użyty do przeprowadzenia testu porównawczego:
źródło
yield from chunk
zamiast tego zakładasz, że takfor byte in chunk: yield byte
? Myślę, że powinienem zaostrzyć moją odpowiedź.yield from
.enumerate
ponieważ należy rozumieć, że iteracja jest zakończona - jeśli nie, to ostatnio sprawdziłem - wyliczenie ma trochę kosztów ogólnych związanych z prowadzeniem księgowości dla indeksu z + = 1, więc możesz alternatywnie wykonywać księgowanie w swoim własny kod. Lub nawet przejść do deque zmaxlen=0
.enumerate
. Dzięki za opinie. Dodam aktualizację do mojego postu, która jej nie ma (chociaż nie sądzę, żeby zmieniała to znacznie wyniki). Będzie również dodanie @Rick M.numpy
opartych odpowiedź.super().
zamiasttuple.
w swoim,__new__
możesz użyćnamedtuple
nazw atrybutów zamiast indeksów.jeśli szukasz czegoś szybkiego, oto metoda, której używałem, która działała przez lata:
jeśli chcesz iterować znaki zamiast ints, możesz po prostu użyć
data = file.read()
, który powinien być obiektem bytes () w py3.źródło