Преобразование Python 2 в 3: перебор строк в подпроцессе stdout


У меня есть следующий пример кода Python 2, который я хочу сделать совместимым с Python 3:

call = 'for i in {1..5}; do sleep 1; echo "Hello $i"; done'
p = subprocess.Popen(call, stdout=subprocess.PIPE, shell=True)
for line in iter(p.stdout.readline, ''):
    print(line, end='')

Это хорошо работает в Python 2, но в Python 3 p.stdout не позволяет мне указать кодировку и чтение будет возвращать байтовые строки, а не Unicode, поэтому сравнение с '' всегда будет возвращать false и iter не остановится. эта проблема , по-видимому, подразумевает, что в Python 3.6 будет способ определить эту кодировку.

На данный момент я изменил вызов iter на остановитесь, когда он найдет пустую байтовую строку iter(p.stdout.readline, b''), которая, кажется, работает в 2 и 3. Мои вопросы таковы: безопасно ли это и во 2, и в 3? Есть ли лучший способ обеспечить совместимость?

Примечание: я не использую for line in p.stdout:, потому что мне нужно, чтобы каждая строка печаталась по мере ее создания и в соответствии с этим ответом p.stdout имеет слишком большой буфер.

2 3

2 ответа:

Вы можете добавить unversal_newlines=True.

p = subprocess.Popen(call, stdout=subprocess.PIPE, shell=True, universal_newlines=True)
for line in iter(p.stdout.readline, ''):
    print(line, end='')

Вместо bytes, str будет возвращен, так что '' будет работать в обеих ситуациях.

Вот что говорят документы об этом варианте:

Если значение universal_newlines равно False, то файловые объекты stdin, stdout и stderr будет открыт как двоичный поток, и без преобразования конца строки сделано.

Если значение universal_newlines равно True, то эти файловые объекты будут открыты как текстовые потоки в режиме универсальных новых строк с использованием кодирование, возвращенное место действия.getpreferredencoding (False). Для stdin, символы окончания строки '\n ' во входных данных будет преобразован в разделитель строк по умолчанию ос.лайнесеп. Для stdout и stderr все окончания строк в выходных данных будут быть преобразованным в '\n'. Для получения дополнительной информации см. документацию Ио.TextIOWrapper класс, когда аргумент новой строки в его конструктор-это никто.

Это не вызвано явно о разнице bytes Против str, но подразумевается утверждая, что False возвращает двоичный поток, а True возвращает текстовый поток.

Вы можете использовать p.communicate(), а затем декодировать его, если это объект bytes:

from __future__ import print_function
import subprocess

def b(t):
    if isinstance(t, bytes):
        return t.decode("utf8")
    return t

call = 'for i in {1..5}; do sleep 1; echo "Hello $i"; done'
p = subprocess.Popen(call, stdout=subprocess.PIPE, shell=True)
stdout, stderr = p.communicate()

for line in iter(b(stdout).splitlines(), ''):
    print(line, end='')

Это будет работать как в Python 2, так и в Python 3