Ответ 1
По умолчанию стек ввода/вывода в Python 2 накладывается поверх потоков CRT FILE
. В Windows они построены поверх API эмуляции POSIX, который использует файловые дескрипторы (которые, в свою очередь, накладываются поверх пользовательского режима Windows API, который накладывается поверх системы ввода-вывода в режиме ядра, которая сама по себе является глубокоуровневой системой на основе пакетов запросов ввода-вывода, аппаратное обеспечение там где-то там...). В POSIX-слое, открывая файл с режимом _O_RDWR | _O_TEXT
(как в "r +" ), требуется поиск конца файла для удаления CTRL + Z, если он присутствует. Здесь приведена цитата из документации CRT fopen
:
Открыть в текстовом (переведенном) режиме. В этом режиме CTRL + Z интерпретируется как символ конца файла на входе. В файлах, открытых для чтения/записи с "a +", fopen проверяет CTRL + Z в конце файла и удаляет его, если это возможно. Это делается потому, что использование fseek и ftell для перемещение внутри файла, заканчивающегося CTRL + Z, может привести к тому, что fseek будет вести себя неправильно рядом с концом файла.
Проблема в том, что указанная выше проверка вызывает 32-битный _lseek
(помните, что sizeof long
- 4 байта на 64-разрядная Windows, в отличие от большинства других 64-разрядных платформ) вместо _lseeki64
. Очевидно, это не удается для 11-гигабайтного файла. В частности, SetFilePointer
терпит неудачу, потому что он вызывается с NULL
значением для lpDistanceToMoveHigh
. Здесь возвращаемое значение и LastErrorValue
для последнего вызова:
0:000> kc 2
Call Site
KERNELBASE!SetFilePointer
MSVCR90!lseek_nolock
0:000> r rax
rax=00000000ffffffff
0:000> dt _TEB @$teb LastErrorValue
ntdll!_TEB
+0x068 LastErrorValue : 0x57
Код ошибки 0x57 ERROR_INVALID_PARAMETER
. Это означает, что lpDistanceToMoveHigh
является NULL
при попытке поиска с конца большого файла.
Чтобы обойти эту проблему с потоками CRT FILE
, я рекомендую открыть файл, используя io.open
. Это резервная реализация стека ввода-вывода Python 3. Он всегда открывает файлы в сыром двоичном режиме (_O_BINARY
), и он реализует свои собственные слои буферизации и текстового режима поверх исходного слоя.
>>> import io
>>> f = io.open('a.csv', 'r+')
>>> f
<_io.TextIOWrapper name='a.csv' encoding='cp1252'>
>>> f.buffer
<_io.BufferedRandom name='a.csv'>
>>> f.buffer.raw
<_io.FileIO name='a.csv' mode='rb+'>
>>> f.seek(0, os.SEEK_END)
11811160064L