python подпроцесс не будет чередовать stderr и stdout как то, что делает терминал - PullRequest
2 голосов
/ 17 февраля 2020

Тестовая программа

#!/usr/bin/env python3

import sys

count = 0
sys.stderr.write('stderr, order %d\n' % count)
count += 1
sys.stdout.write('stdout, order %d\n' % count)
count += 1
sys.stderr.write('stderr, order %d\n' % count)
count += 1
sys.stdout.write('stdout, order %d\n' % count)

при вызове через терминал ожидаемый результат:

stderr, order 0
stdout, order 1
stderr, order 2
stdout, order 3

В интерактивной оболочке, когда я перенаправляю stdout в ТРУБУ, порядок вывода отличается от вывода выше, где Popen группирует stderr и записывает их все, а затем делает то же самое для stdout вместо чередования stdout и stderr.

In [29]: a = sp.run(['./test.py'], stderr=sp.STDOUT)
stderr, order 0
stdout, order 1
stderr, order 2
stdout, order 3

In [30]: a
Out[30]: CompletedProcess(args=['./test.py'], returncode=0)

In [33]: b = sp.Popen(['./test.py'], stderr=sp.STDOUT, stdout=sp.PIPE, encoding='utf-8')

In [34]: print(b.communicate()[0])
stderr, order 0
stderr, order 2
stdout, order 1
stdout, order 3

1 Ответ

1 голос
/ 17 февраля 2020

В библиотеках C (и, следовательно, c на основе python) потоки обрабатываются по-разному в зависимости от того, подключены они к интерактивным терминалам (или что-то притворяется) или нет. Для tty, stdout является буферизованной строкой, в противном случае его блок буферизуется и сбрасывается в дескриптор файла только при достижении некоторой границы блока. Когда вы перенаправлены на PIPE, поток больше не является tty, и действует блочная буферизация.

Решение состоит в том, чтобы снова открыть stdout, указав, что вы хотите буферизацию строки (1) независимо от этого. На уровне C, stderr всегда буферизуется строкой, но когда я тестировал только повторное открытие stdout, программа действовала так, как если бы stderr была буферизована блоком. Я был довольно удивлен. Может быть, это промежуточный слой io.TextIO или какая-то другая странная вещь, но я обнаружил, что мне нужно исправить обе трубы.

Даже если stdout и stderr go к одной трубе, они отдельные файловые дескрипторы с отдельными буферами в отношении исполняемой программы. Вот почему перемежение не происходит естественным образом в выходном буфере даже в блочном режиме.

#!/usr/bin/env python3

import sys
import os

# reopen stdout line buffered
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 1)

# this surprises me, seems like we have to reopen stderr
# line buffered, but i thought it was line buffered anywy.
# perhaps its the intermediate python TextIO layer?
sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 1)

count = 0
sys.stderr.write('stderr, order %d\n' % count)
count += 1
sys.stdout.write('stdout, order %d\n' % count)
count += 1
sys.stderr.write('stderr, order %d\n' % count)
count += 1
sys.stdout.write('stdout, order %d\n' % count)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...