2016-02-17 12 views
8

Piszę skrypt do przetwarzania plików XI EDI, które chciałbym iterować wiersz po wierszu. Pliki składają się z sekwencji odrębnych rekordów, z których każdy zakończony jest specjalnym znakiem (np. ~, ale patrz poniżej). Pliki mogą być duże (> 100 MB), więc nie chcę czytać całej rzeczy i dzielić ją. Rekordy nie są rozdzielane przez znaki nowej linii; czytanie w pierwszej linii prawdopodobnie odczytałoby cały plik. Pliki są w całości ASCII.Czytaj plik do postaci

Python wyraźnie zapewnia odczyt pliku do określonej postaci, pod warunkiem, że jest to znak nowej linii. Chciałbym zrobić to samo z arbitralnym charakterem. Zakładam, że czytanie po linii jest realizowane poprzez buforowanie. Mogłabym wdrożyć mój własny buforowany czytnik, ale wolałbym uniknąć dodatkowego kodu i kosztów ogólnych, jeśli istnieje lepsze rozwiązanie.

Uwaga: Widziałem kilka podobnych pytań, ale wszystkie zdawały się wskazywać, że należy czytać plik w wierszu, zakładając, że linie będą miały rozsądny rozmiar. W tym przypadku cały plik będzie prawdopodobnie jedną linią.

Edytuj: Znak końca segmentu jest równy 106 bajtowi pliku. Nie wiadomo, zanim skrypt zostanie wywołany.

+3

Może użyć '.read (some_reasonable_number)' na wskaźniku pliku i przeszukać wynik dopóki nie znajdziesz swojego '~', a jeśli to zrobisz, '.seek()' do tyłu? – L3viathan

+0

Wierzę, że masz rację co do buforowanego czytnika, a niestety argument 'newline' w' open' nie zezwala na '~'. –

+2

Obawiam się, że będziesz musiał napisać dedykowaną implementację przez io.BytesIO, czytając plik w kawałkach i wysyłając * linię * zakończoną określonym ogranicznikiem ... –

Odpowiedz

2

To wciąż jest daleka od optymalnej, ale byłoby to realizacja czystej Python z bardzo prostego bufora:

def my_open(filename, char): 
    with open(filename) as f: 
     old_fb="" 
     for file_buffer in iter(lambda: f.read(1024), ''): 
      if old_fb: 
       file_buffer = old_fb + file_buffer 
      pos = file_buffer.find(char) 
      while pos != -1 and file_buffer: 
       yield file_buffer[:pos] 
       file_buffer = file_buffer[pos+1:] 
       pos = file_buffer.find(char) 
      old_fb = file_buffer 
     yield old_fb 

# Usage: 
for line in my_open("weirdfile", "~"): 
    print(line) 
4

Jeśli nie będą nowe linie w pliku na początek, przekształcić plik przed potokiem go do skryptu Pythona, np

tr '~' '\n' < source.txt | my-script.py 

następnie wykorzystać readline(), readlines() lub for line in file_object: jako odpowiedni .

+1

A jeśli program pobiera plik z wiersza poleceń , nie stdin, przynajmniej w bashu możesz użyć podstawienia procesu: 'my-script.py <(tr '~' '\ n' ShadowRanger

+0

Terminator segmentu nie jest znany w momencie wywołania skryptu. Wyjaśniłem pierwotne pytanie. Ponadto skrypt ten będzie działał w środowisku systemu Windows, które nie jest mi obce, więc standardowe narzędzia UNIX mogą być niedostępne. Poza tym, czy ta substytucja procesu w żaden sposób nie sprawiłaby, że cały plik byłby w buforze, gdyby skrypt Pythona nie odczytał go wystarczająco szybko? –

0

Może trzeba coś takiego

Ostrzeżenie: kod nie zoptymalizowanej

class File(object): 

    def __init__(self, f): 
     self.file = f 

    def readline(self, delimiter='~'): 
     buffer = '' 
     while True: 
      b = self.file.read(1) 
      buffer += b 
      if b: 
       if b == delimiter: 
        yield buffer 
        buffer = '' 
      else: 
       yield buffer 
       break