Как установить кодировку sys.stdout в Python 3?

Настройка кодировки вывода по умолчанию в Python 2 является известной идиомой:

sys.stdout = codecs.getwriter("utf-8")(sys.stdout)

Это обертывает объект sys.stdout в записи кодека, который кодирует вывод в UTF-8.

Однако этот метод не работает в Python 3, потому что sys.stdout.write() ожидает str, но результатом кодирования является bytes, и возникает ошибка, когда codecs пытается записать закодированные байты в исходное sys.stdout.

Каков правильный способ сделать это в Python 3?

Ответ 1

Начиная с Python 3.7 вы можете изменить кодировку стандартных потоков с помощью reconfigure():

sys.stdout.reconfigure(encoding='utf-8')

Вы также можете изменить способ обработки ошибок кодирования путем добавления параметра errors.

Ответ 2

Python 3.1 добавил io.TextIOBase.detach() с примечанием в документации для sys.stdout:

Стандартные потоки по умолчанию находятся в текстовом режиме. Чтобы записать или прочитать двоичные данные, используйте базовый двоичный буфер. Например, чтобы записать байты в stdout, используйте sys.stdout.buffer.write(b'abc'). Использование потоков io.TextIOBase.detach() по умолчанию может быть сделано двоичным. Эта функция устанавливает stdin и stdout в двоичный:

def make_streams_binary():
    sys.stdin = sys.stdin.detach()
    sys.stdout = sys.stdout.detach()

Следовательно, соответствующая идиома для Python 3.1 и более поздних версий:

sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())

Ответ 3

Я нашел этот поток при поиске решений с той же ошибкой,

Альтернативным решением для уже предложенных является установка переменной PYTHONIOENCODING environment до Python, для моего использования - это меньше проблем, чем замена sys.stdout после инициализации Python:

PYTHONIOENCODING=utf-8:surrogateescape python3 somescript.py

С тем, что вам не нужно идти и редактировать код Python.

Ответ 4

Другие ответы, по-видимому, рекомендуют использовать codecs, но open работает для меня:

import sys
sys.stdout = open(sys.stdout.fileno(), mode='w', encoding='utf8', buffering=1)
print("日本語")
# Also works with other methods of writing to stdout:
sys.stdout.write("日本語\n")
sys.stdout.buffer.write("日本語\n".encode())

Это работает, даже когда я запускаю его с помощью PYTHONIOENCODING="ascii".

Ответ 5

Настройка кодировки вывода по умолчанию в Python 2 является известной идиомой

Ик! Это известная идиома в Python 2? Это выглядит как опасная ошибка.

Это наверняка испортит любой script, который пытается записать двоичный файл в stdout (который вам понадобится, если вы, например, возвращаете изображение CGI script). Байты и символы - совсем другие животные; это не очень хорошая идея для monkey-patch интерфейса, который указан для принятия байтов с тем, который принимает только символы.

CGI и HTTP в целом явно работают с байтами. Вы должны отправлять байты только в sys.stdout. В Python 3, что означает использование sys.stdout.buffer.write для отправки байтов напрямую. Кодирование содержимого страницы, соответствующее его параметру charset, должно обрабатываться на более высоком уровне в вашем приложении (в случаях, когда вы возвращаете текстовый контент, а не двоичный). Это также означает, что print больше не подходит для CGI.

(Чтобы добавить к путанице, wsgiref CGIHandler был взломан в py3k до недавнего времени, что делает невозможным развертывание WSGI в CGI таким образом. С PEP 3333 и Python 3.2 это, наконец, возможно.)

Ответ 6

Использование detach() заставляет интерпретатор печатать предупреждение, когда он пытается закрыть stdout непосредственно перед его выходом:

Exception ignored in: <_io.TextIOWrapper mode='w' encoding='UTF-8'>
ValueError: underlying buffer has been detached

Вместо этого это сработало для меня:

default_out = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

(И, конечно, запись в default_out вместо stdout.)

Ответ 7

sys.stdout находится в текстовом режиме в Python 3. Следовательно, вы пишите в него unicode напрямую, и идиома для Python 2 больше не нужна.

Если это не удалось в Python 2:

>>> import sys
>>> sys.stdout.write(u"ûnicöde")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xfb' in position 0: ordinal not in range(128)

Однако, он работает просто dandy в Python 3:

>>> import sys
>>> sys.stdout.write("Ûnicöde")
Ûnicöde7

Теперь, если ваш Python не знает, какова ваша стандартная кодировка stdouts, это другая проблема, скорее всего, в сборке Python.