Небуферизованное чтение со стандартного ввода в python
Я пишу сценарий Python, который может читать ввод через канал из другой команды, например, так
batch_job | myparser
Мой сценарий myparser
обрабатывает вывод batch_job
и написать в свой стандартный вывод. Моя проблема в том, что я хочу увидеть вывод немедленно (вывод batch_job обрабатывается построчно), но, похоже, существует эта пресловутая буферизация stdin (якобы 4KB, я не проверял), которая задерживает все.
Проблема уже обсуждалась здесь и здесь.
Я попробовал следующее:
- открыть стандартный ввод
os.fdopen(sys.stdin.fileno(), 'r', 0)
- с помощью
-u
в моем hashbang:#!/usr/bin/python -u
- установка
export PYTHONUNBUFFERED=1
прямо перед вызовом скрипта - сбрасывать вывод после каждой прочитанной строки (на случай, если проблема связана с буферизацией вывода, а не с буферизацией ввода)
Моя версия Python 2.4.3 - у меня нет возможности обновлять или устанавливать какие-либо дополнительные программы или пакеты. Как я могу избавиться от этих задержек?
1 ответ
Я столкнулся с той же проблемой с устаревшим кодом. Похоже, это проблема с реализацией Python 2
file
объекты
__next__
метод; он использует буфер уровня Python (который
-u
/PYTHONUNBUFFERED=1
не влияет, потому что они только снимают буферизацию
stdio
FILE*
сами, но
file.__next__
буферизация не связана; так же,
stdbuf
/unbuffer
вообще не может изменить какую-либо буферизацию, потому что Python заменяет буфер по умолчанию, созданный средой выполнения C; Последнее дело file.__init__
делает для вновь открытого файла вызов
PyFile_SetBufSize
который использует
setvbuf
/setbuf
[API] для замены по умолчанию
stdio
буфер).
Проблема видна, когда у вас есть цикл формы:
for line in sys.stdin:
где первый звонок
__next__
(вызывается неявно
for
цикл, чтобы получить каждый
line
) заканчивается блокировкой, чтобы заполнить блок перед созданием одной строки.
Есть три возможных исправления:
(Только на Python 2.6+)
sys.stdio
сio
модуль (перенесенный из Python 3 как встроенный) для обходаfile
полностью в пользу (откровенно превосходного) дизайна Python 3 (который использует один системный вызов за раз для заполнения буфера без блокировки для выполнения полного запрошенного чтения; если он запрашивает 4096 байтов и получает 3, он увидит если строка доступна и произведите ее, если так) так:import io import sys # Add buffering=0 argument if you won't always consume stdin completely, so you # can't lose data in the wrapper's buffer. It'll be slower with buffering=0 though. with io.open(sys.stdin.fileno(), 'rb', closefd=False) as stdin: for line in stdin: # Do stuff with the line
Обычно это быстрее, чем вариант 2, но он более подробный и требует Python 2.6+. Это также позволяет использовать Unicode для перенастройки, изменив режим на
'r'
и при желании передать известныеencoding
ввода (если это не локаль по умолчанию), чтобы легко получитьunicode
строки вместо (только ASCII)str
.(Любая версия Python) Решение проблем с
file.__next__
используяfile.readline
вместо; несмотря на почти идентичное предполагаемое поведение,readline
не выполняет свою собственную (избыточную) буферизацию, он делегирует Cstdio
сfgets
(настройки сборки по умолчанию) или ручной вызов циклаgetc
/getc_unlocked
в буфер, который останавливается именно тогда, когда достигает конца строки. Объединив его с двумя аргументамиiter
вы можете получить почти идентичный код без лишней многословности (возможно, это будет медленнее, чем предыдущее решение, в зависимости от того,fgets
используется под капотом, и как среда выполнения C реализует его):# '' is the sentinel that ends the loop; readline returns '' at EOF for line in iter(sys.stdin.readline, ''): # Do stuff with line
Перейдите на Python 3, в котором этой проблемы нет.:-)
В Linux bash, то, что вы ищете, кажется командой stdbuf.
Если вы не хотите буферизации (то есть небуферизованного потока), попробуйте это,
# batch_job | stdbuf -o0 myparser
Если вы хотите буферизацию строки, попробуйте это,
# batch_job | stdbuf -oL myparser