Как установить кодировку 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?

7 ответов

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

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

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

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())

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

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

PYTHONIOENCODING=utf-8:surrogateescape python3 somescript.py

С преимуществом не нужно идти и редактировать код Python.

Другие ответы рекомендуют использовать 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",

Установка выходной кодировки по умолчанию в Python 2 - это хорошо известная идиома

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

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

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

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

С помощью detach() заставляет интерпретатор вывести предупреждение, когда он пытается закрыть стандартный вывод непосредственно перед выходом:

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 вместо стандартного выхода.)

sys.stdout находится в текстовом режиме в Python 3. Следовательно, вы пишете в него юникод напрямую, и идиома для 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)

Тем не менее, это работает просто денди в Python 3:

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

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

Другие вопросы по тегам