Установка правильной кодировки при передаче стандартного вывода в Python

343

При передаче вывода программы Python интерпретатор Python запутывается в кодировании и устанавливает для него значение None. Это означает такую ​​программу:

# -*- coding: utf-8 -*-
print u"åäö"

будет нормально работать при нормальной работе, но не с:

UnicodeEncodeError: кодек 'ascii' не может кодировать символ u '\ xa0' в позиции 0: порядковый номер не в диапазоне (128)

при использовании в последовательности труб.

Каков наилучший способ сделать эту работу при обвязке? Могу ли я просто сказать ему использовать любую кодировку оболочки / файловой системы / что бы она ни использовала?

Советы, которые я видел до сих пор, это изменить ваш site.py напрямую или жестко закодировать код по умолчанию с помощью этого хака:

# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
print u"åäö"

Есть ли лучший способ заставить трубопровод работать?

Йоаким Лундборг
источник
1
Смотрите также stackoverflow.com/questions/4545661/...
ShreevatsaR
2
Если у вас есть эта проблема на окнах, вы также можете запустить chcp 65001перед выполнением сценария. Это может иметь проблемы, но это часто помогает, и не требует большого набора текста (меньше, чем set PYTHONIOENCODING=utf_8).
Томаш Гандор
Команда chcp отличается от настройки PYTHONIOENCODING. Я думаю, что chcp - это просто конфигурация для самого терминала, и он не имеет ничего общего с записью в файл (что вы и делаете, когда передаете стандартный вывод). Попробуйте setx PYTHONENCODING utf-8сделать его постоянным, если вы хотите сохранить набор текста.
EJM
Я столкнулся с несколько связанной проблемой и нашел решение здесь -> stackoverflow.com/questions/48782529/…
bkrishna2006

Ответы:

162

Ваш код работает при запуске в скрипте, потому что Python кодирует выходные данные в любую кодировку, используемую вашим терминальным приложением. Если вы используете трубопровод, вы должны закодировать его самостоятельно.

Практическое правило: всегда используйте Unicode для внутреннего использования. Расшифруйте то, что вы получаете, и закодируйте то, что вы отправляете.

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

Другим дидактическим примером является программа на Python, которая конвертирует между ISO-8859-1 и UTF-8, делая все в верхнем регистре между ними.

import sys
for line in sys.stdin:
    # Decode what you receive:
    line = line.decode('iso8859-1')

    # Work with Unicode internally:
    line = line.upper()

    # Encode what you send:
    line = line.encode('utf-8')
    sys.stdout.write(line)

Установка системной кодировки по умолчанию - плохая идея, потому что некоторые модули и библиотеки, которые вы используете, могут полагаться на факт, что это ASCII. Не делай этого.

nosklo
источник
11
Проблема в том, что пользователь не хочет явно указывать кодировку. Он хочет просто использовать Unicode для ввода-вывода. И кодировка, которую он использует, должна быть кодировкой, указанной в настройках локали, а не в настройках приложения терминала. AFAIK, Python 3 использует кодировку локали в этом случае. Изменение sys.stdoutкажется более приятным способом.
Андрей Власовских
4
Кодирование / декодирование каждой строки безоговорочно должно вызывать ошибки, когда вызов кодирования или декодирования отсутствует или добавлен один раз к чему-то еще. Кодировка выхода может быть установлена, когда выходной сигнал является терминалом, поэтому он может быть установлен, когда выходной сигнал не является терминалом. Существует даже стандартная среда LC_CTYPE для ее указания. Но в питоне это не уважает.
Расмус Кай
65
Этот ответ неверен. Вы не должны вручную конвертировать каждый вход и выход вашей программы; это хрупко и совершенно не поддается ремонту.
Гленн Мейнард
29
@ Гленн Мейнард: так что же такое IYO правильный ответ?
Намного полезнее
14
@smci: ответ не изменяет ваш скрипт, установите, PYTHONIOENCODINGесли вы перенаправляете стандартный вывод скрипта в Python 2.
jfs
168

Во-первых, относительно этого решения:

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

Непрактично каждый раз явно печатать с заданной кодировкой. Это было бы повторяющимся и подверженным ошибкам.

Лучшее решение - изменить sys.stdoutв начале вашей программы кодирование с выбранной кодировкой. Вот одно решение, которое я нашел на Python: Как выбрать sys.stdout.encoding? , в частности комментарий от "Тока":

import sys
import codecs
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
Крейг МакКуин
источник
7
К сожалению, изменение sys.stdout для принятия только Unicode нарушает работу многих библиотек, которые ожидают, что он принимает закодированные строки.
nosklo
6
nosklo: Тогда как это может работать надежно и автоматически, когда вывод является терминалом?
Расмус Кай
3
@Rasmus Kaj: просто определите свою собственную функцию печати Unicode и используйте ее каждый раз, когда вы хотите напечатать Unicode: def myprint(unicodeobj): print unicodeobj.encode('utf-8')- вы автоматически обнаруживаете кодирование терминала, проверяя sys.stdout.encoding, но вы должны учитывать тот случай, когда он есть None(т.е. при перенаправлении вывода в файл) так что вам нужна отдельная функция в любом случае.
Носкло
3
@nosklo: это не заставляет sys.stdout принимать только Unicode. Вы можете передавать как str, так и unicode в StreamWriter.
Гленн Мейнард
9
Я предполагаю, что этот ответ был предназначен для python2. Будьте осторожны с этим в коде, который предназначен для поддержки как python2, так и python3 . Для меня это ломает вещи, когда работает под python3.
Вим
130

Вы можете попробовать изменить переменную среды "PYTHONIOENCODING" на "utf_8". Я написал страницу в моем испытании с этой проблемой .

Tl; dr поста в блоге:

import sys, locale, os
print(sys.stdout.encoding)
print(sys.stdout.isatty())
print(locale.getpreferredencoding())
print(sys.getfilesystemencoding())
print(os.environ["PYTHONIOENCODING"])
print(chr(246), chr(9786), chr(9787))

дает тебе

utf_8
False
ANSI_X3.4-1968
ascii
utf_8
ö ☺ ☻
daveagp
источник
2
Изменение sys.stdout.encoding может быть , не работает, но изменение sys.stdout делает работу: sys.stdout = codecs.getwriter(encoding)(sys.stdout). Это может быть сделано из программы Python, поэтому пользователь не обязан устанавливать переменную env.
BlueFast
7
@ jeckyll2hide: PYTHONIOENCODINGработает. То, как байты интерпретируются как текст, определяется пользовательской средой. Ваш сценарий не должен предполагать и определять пользовательскую среду, какую кодировку символов использовать. Если Python не выбирает настройки автоматически, это PYTHONIOENCODINGможно сделать для вашего скрипта. Вам это не нужно, если вывод не перенаправлен в файл / канал.
Jfs
8
+1. Честно говоря, я думаю, что это ошибка Python. Когда я перенаправляю вывод, я хочу те же байты, которые будут на терминале, но в файле. Может быть, это не для всех, но это хороший дефолт. Сбой при отсутствии объяснения тривиальной операции, которая обычно «просто работает», является плохим значением по умолчанию.
SnakE
@SnakE: единственный способ объяснить, почему реализация Python преднамеренно привела в действие железный и постоянный выбор кодировки на stdout во время запуска, может быть для того, чтобы предотвратить появление плохо закодированных вещей позже. Или его изменение - просто невыполненная функция, и в этом случае предоставление пользователю возможности изменить его позже будет разумным запросом функции Python.
daveagp
2
@ daveagp Моя точка зрения такова, что поведение моей программы не должно зависеть от того, перенаправлена ​​ли она или нет - если только я действительно этого не хочу, и в этом случае я сам ее реализую. Python ведет себя вопреки моему опыту с любыми другими консольными инструментами. Это нарушает принцип наименьшего удивления. Я считаю это недостатком дизайна, если только нет очень веского обоснования.
SnakE
62
export PYTHONIOENCODING=utf-8

сделать работу, но не могу установить ее на самом Python ...

то, что мы можем сделать, это проверить, если не установлен, и сказать пользователю установить его перед вызовом скрипта с помощью:

if __name__ == '__main__':
    if (sys.stdout.encoding is None):
        print >> sys.stderr, "please set python env PYTHONIOENCODING=UTF-8, example: export PYTHONIOENCODING=UTF-8, when write to stdout."
        exit(1)

Обновление, чтобы ответить на комментарий: проблема просто существует при передаче на стандартный вывод. Я тестировал в Fedora 25 Python 2.7.13

python --version
Python 2.7.13

кот б.пи

#!/usr/bin/env python
#-*- coding: utf-8 -*-
import sys

print sys.stdout.encoding

работает ./b.py

UTF-8

работает ./b.py | Меньше

None
Сержио
источник
2
Эта проверка не работает в Python 2.7.13. sys.stdout.encodingавтоматически устанавливается на основе LC_CTYPEзначения локали.
амфетамина
1
mail.python.org/pipermail/python-list/2011-June/605938.html пример все еще работает, т.е. когда вы используете ./a.py> out.txt sys.stdout.encoding is None
Sérgio
У меня была похожая проблема со скриптом синхронизации из Backblaze B2, и экспорт PYTHONIOENCODING = utf-8 решил мою проблему. Python 2.7 на Debian Stretch.
0x3333
5

У меня была похожая проблема на прошлой неделе . Это было легко исправить в моей IDE (PyCharm).

Вот мое исправление:

Начиная с строки меню PyCharm: Файл -> Настройки ... -> Редактор -> Кодировки файлов, затем установите: «Кодировка IDE», «Кодировка проекта» и «Кодировка по умолчанию для файлов свойств» ВСЕ в UTF-8, и теперь она работает Как колдовство.

Надеюсь это поможет!

CLaFarge
источник
4

Спорная санированная версия ответа Крейга МакКуина.

import sys, codecs
class EncodedOut:
    def __init__(self, enc):
        self.enc = enc
        self.stdout = sys.stdout
    def __enter__(self):
        if sys.stdout.encoding is None:
            w = codecs.getwriter(self.enc)
            sys.stdout = w(sys.stdout)
    def __exit__(self, exc_ty, exc_val, tb):
        sys.stdout = self.stdout

Использование:

with EncodedOut('utf-8'):
    print u'ÅÄÖåäö'
Tompa
источник
2

Я мог бы "автоматизировать" это с помощью вызова:

def __fix_io_encoding(last_resort_default='UTF-8'):
  import sys
  if [x for x in (sys.stdin,sys.stdout,sys.stderr) if x.encoding is None] :
      import os
      defEnc = None
      if defEnc is None :
        try:
          import locale
          defEnc = locale.getpreferredencoding()
        except: pass
      if defEnc is None :
        try: defEnc = sys.getfilesystemencoding()
        except: pass
      if defEnc is None :
        try: defEnc = sys.stdin.encoding
        except: pass
      if defEnc is None :
        defEnc = last_resort_default
      os.environ['PYTHONIOENCODING'] = os.environ.get("PYTHONIOENCODING",defEnc)
      os.execvpe(sys.argv[0],sys.argv,os.environ)
__fix_io_encoding() ; del __fix_io_encoding

Да, здесь можно получить бесконечный цикл, если этот "setenv" не работает.

Jno
источник
1
интересно, но труба, кажется, не рада этому
n611x007
2

Я просто подумал, что упомяну здесь кое-что, с чем мне пришлось долго экспериментировать, прежде чем я наконец понял, что происходит. Это может быть настолько очевидным для всех здесь, что они не потрудились упомянуть об этом. Но это помогло бы мне, если бы они имели, так по этому принципу ...!

NB: я использую Jython специально, v 2.7, так что, возможно, это не относится к CPython ...

NB2: первые две строки моего файла .py здесь:

# -*- coding: utf-8 -*-
from __future__ import print_function

Механизм построения строки "%" (AKA "оператор интерполяции") также вызывает ДОПОЛНИТЕЛЬНЫЕ проблемы ... Если кодировка "среды" по умолчанию - ASCII, и вы пытаетесь сделать что-то вроде

print( "bonjour, %s" % "fréd" )  # Call this "print A"

У вас не будет проблем с запуском в Eclipse ... В CLI Windows (окно DOS) вы обнаружите, что кодировка - это кодовая страница 850 (моя ОС Windows 7) или что-то подобное, которая может обрабатывать европейские символы с акцентом по крайней мере, поэтому буду работать

print( u"bonjour, %s" % "fréd" ) # Call this "print B"

тоже будет работать.

Если, OTOH, вы перенаправляете файл из CLI, кодировкой stdout будет None, которая по умолчанию будет ASCII (в любом случае, в моей ОС), которая не сможет обрабатывать ни один из вышеперечисленных отпечатков ... (страшная кодировка ошибка).

Итак, вы можете подумать о перенаправлении стандартного вывода с помощью

sys.stdout = codecs.getwriter('utf8')(sys.stdout)

и попробуйте запустить в CLI трубопровод к файлу ... Как ни странно, печать A выше будет работать ... Но печать B выше вызовет ошибку кодирования! Следующее, однако, будет работать нормально:

print( u"bonjour, " + "fréd" ) # Call this "print C"

Вывод, к которому я пришел (условно), заключается в том, что если строка, указанная как строка Unicode с префиксом «u», передается механизму% -обработки, то она, по-видимому, предполагает использование кодировки среды по умолчанию, независимо от установили ли вы stdout для перенаправления!

Как люди справляются с этим - вопрос выбора. Я хотел бы, чтобы эксперт по Unicode рассказал, почему это происходит, правильно ли я понял это, каким образом это предпочтительное решение, применимо ли это также к CPython , происходит ли это в Python 3 и т. Д., И т. Д., И т. Д.

Майк Грызун
источник
Это не странно, потому что "fréd"это последовательность байтов, а не строка в Юникоде, поэтому codecs.getwriterобертка оставит ее в покое. Вам нужен ведущий u, или from __future__ import unicode_literals.
Матиас Урлихс
@MatthiasUrlichs ОК ... спасибо ... Но я просто нахожу кодирование одним из самых ярких аспектов ИТ. Откуда вы получаете свое понимание? Например, я только что опубликовал еще один вопрос о кодировании здесь: stackoverflow.com/questions/44483067/… : это о Java, Eclipse, Cygwin & Gradle. Если ваши знания зашли так далеко, пожалуйста, помогите ... прежде всего я хотел бы узнать, где можно узнать больше!
Майк Грызун
1

Я столкнулся с этой проблемой в унаследованном приложении, и было трудно определить, где что было напечатано. Я помог себе с этим взломать:

# encoding_utf8.py
import codecs
import builtins


def print_utf8(text, **kwargs):
    print(str(text).encode('utf-8'), **kwargs)


def print_utf8(fn):
    def print_fn(*args, **kwargs):
        return fn(str(*args).encode('utf-8'), **kwargs)
    return print_fn


builtins.print = print_utf8(print)

Поверх моего скрипта test.py:

import encoding_utf8
string = 'Axwell Λ Ingrosso'
print(string)

Обратите внимание, что это изменяет ВСЕ вызовы на печать для использования кодировки, поэтому ваша консоль напечатает это:

$ python test.py
b'Axwell \xce\x9b Ingrosso'
cessor
источник
1

В Windows эта проблема возникала очень часто при запуске кода Python из редактора (например, Sublime Text), но не при запуске из командной строки.

В этом случае проверьте параметры вашего редактора. В случае SublimeText это Python.sublime-buildрешило это:

{
  "cmd": ["python", "-u", "$file"],
  "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
  "selector": "source.python",
  "encoding": "utf8",
  "env": {"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
}
Basj
источник