2014-06-28 5 views
9

Mam obiekt file, który może ale nie musi być otwarty w trybie uniwersalnym. (Mogę uzyskać dostęp do tego trybu za pomocą file.mode, jeśli to pomaga).Jak korzystać z prymitywów io (seek, read) w strumieniu pliku, który może być w trybie uniwersalnym?

Chcę zająć się tym plikiem, używając standardowych metod: io: read i seek.

Gdybym otworzyć plik w trybie non-uniwersalnej, wszystko działa dobrze:

In [1]: f = open('example', 'r') 

In [2]: f.read() 
Out[2]: 'Line1\r\nLine2\r\n' # uhoh, this file has carriage returns 

In [3]: f.seek(0) 

In [4]: f.read(8) 
Out[4]: 'Line1\r\nL' 

In [5]: f.seek(-8, 1) 

In [6]: f.read(8) 
Out[6]: 'Line1\r\nL' # as expected, this is the same as before 

In [7]: f.close() 

Jednakże, jeśli mogę otworzyć plik w trybie powszechnych, mamy problem:

In [8]: f = open('example', 'rU') 

In [9]: f.read() 
Out[9]: 'Line1\nLine2\n' # no carriage returns - thanks, 'U'! 

In [10]: f.seek(0) 

In [11]: f.read(8) 
Out[11]: 'Line1\nLi' 

In [12]: f.seek(-8, 1) 

In [13]: f.read(8) 
Out[13]: 'ine1\nLin' # NOT the same output, as what we read as '\n' was *2* bytes 

Python interpretuje \r\n jako \n i zwraca ciąg o długości 8.

Jednak tworzenie tego ciągu obejmowało czytanie 9 bajtów z pliku.

W rezultacie, przy próbie cofnięcia read przy użyciu seek, nie wracamy do miejsca, w którym zaczęliśmy!


Czy istnieje sposób na stwierdzenie, że zużyliśmy 2-bajtowy znak nowej linii lub, jeszcze lepiej, wyłączamy to zachowanie?

Najlepszym mogę wymyślić w tej chwili jest, aby zrobić tell przed i po odczycie i sprawdź ile rzeczywiście dostał, ale to wydaje się bardzo nieeleganckie.


Tak na marginesie, to wydaje mi się, że takie zachowanie jest rzeczywiście sprzeczne z dokumentacją read:

In [54]: f.read? 
Type:  builtin_function_or_method 
String Form:<built-in method read of file object at 0x1a35f60> 
Docstring: 
read([size]) -> read at most size bytes, returned as a string. 

If the size argument is negative or omitted, read until EOF is reached. 
Notice that when in non-blocking mode, less data than what was requested 
may be returned, even if no size parameter was given. 

Do mojego czytania, który sugeruje, że co najwyżej rozmiar bajtów powinny być czytać , nie zwrócił.

W szczególności uważam, że prawidłowe semantyka powyższym przykładzie powinno być:

In [11]: f.read(8) 
Out[11]: 'Line1\nL' # return a string of length *7* 

Am I nieporozumienia dokumentację?

+2

Tryb I/O w trybie tekstowym został stworzony przez diabła w celu udaremnienia schematów rodzaju ludzkiego. Unikaj go jak ognia. – user3553031

+1

To, o co prosisz, nie jest możliwe. Wystarczy otworzyć plik w trybie binarnym i pracować z tym. Lub użyj 'io.open (..., newline = '\ n')', aby wyłączyć uniwersalne znaki nowej linii. – Bakuriu

Odpowiedz

0

Tutaj wymienię obejście problemu, chociaż w żadnym wypadku nie jestem usatysfakcjonowany.

Biorąc pod uwagę, że podstawowym problemem jest rozbieżność pomiędzy długością \n w trybie uniwersalnym, a liczbą bajtów faktycznie reprezentowanych w pliku, jednym ze sposobów uniknięcia błędu jest odczytanie z pośredniego strumienia, dla którego \n faktycznie nie stanowią jeden bajt:

def wrap_stream(f): 
    # if this stream is a file, it's possible to just throw the contents in 
    # another stream 
    # alternatively, we could implement an io object which used a generator to 
    # read lines from f and interpose newlines as required 
    return StringIO(f.read()) 

nowy io obiekt zwrócony z wrap_stream pokaże nowe linie jak \n, bez względu na tryb plik został otwarty.

1

Co tak naprawdę próbujesz zrobić?

Jeśli powodem do czytania do przodu, a następnie szukania wstecz, jest to, że chcesz powrócić do określonego punktu w pliku, użyj funkcji tell() w celu zapisania, gdzie jesteś. To łatwiejsze niż śledzenie liczby przeczytanych bajtów.

savepos = f.tell() 
f.read(8) 
f.seek(savepos) 
f.read(8) 
0

byłoby dopuszczalne wykorzystanie fdopen, aby otrzymać nowy obiekt pliku na istniejącym deskryptorze, ale bez przestępstwa trybie „U”, i użyć jej do poszukiwania? Na przykład:

>>> import os 
>>> f=open('example','rU') 
>>> f.read() 
'Line1\nLine2\n' 
>>> ff=os.fdopen(f.fileno(),'r') 
>>> ff.seek(0) 
>>> ff.read() 
'Line1\r\nLine2\r\n' 
>>> ff.seek(-7,1) 
>>> f.read() 
'Line2\n' 
>>> 

Możesz mieć plik w dowolnym trybie bez konieczności jego zamykania i ponownego otwierania w tym trybie.

Powiązane problemy