UnicodeDecodeError при перенаправлении в файл - PullRequest
96 голосов
/ 28 декабря 2010

Я запускаю этот фрагмент дважды в терминале Ubuntu (кодировка установлена ​​в utf-8), один раз с ./test.py, а затем с ./test.py >out.txt:

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni

Без перенаправления печатает мусор. С перенаправлением я получаю UnicodeDecodeError. Может кто-нибудь объяснить, почему я получаю ошибку только во втором случае, или даже лучше дать подробное объяснение того, что происходит за занавесом в обоих случаях?

Ответы [ 3 ]

246 голосов
/ 28 декабря 2010

Весь ключ к таким проблемам кодирования заключается в понимании того, что в принципе существует два различных понятия "строка" : (1) строка из символов и (2) строка/ массив байт .Это различие в основном долгое время игнорировалось из-за исторической распространенности кодировок, содержащих не более 256 символов (ASCII, Latin-1, Windows-1252, Mac OS Roman ...): эти кодировки отображают набор общих символов вчисла от 0 до 255 (т.е. байты);относительно ограниченный обмен файлами до появления Интернета сделал эту ситуацию несовместимых кодировок допустимой, поскольку большинство программ могли игнорировать тот факт, что было несколько кодировок, если они производили текст, который оставался в одной и той же операционной системе: такие программы простообрабатывать текст как байты (через кодировку, используемую операционной системой).Правильное, современное представление должным образом разделяет эти две концепции строк, основываясь на следующих двух моментах:

  1. Символы в основном не связаны с компьютерами :их можно нарисовать на доске, например, بايثون, 中 蟒 и ?.«Знаки» для машин также включают «инструкции рисования», такие как, например, пробелы, возврат каретки, инструкции по установке направления письма (для арабского языка и т. Д.), Ударения и т. Д. * очень большой список символов включенв стандарте Unicode ;он охватывает большинство известных символов.

  2. С другой стороны, компьютерам необходимо каким-то образом представлять абстрактные символы: для этого они используют массивы байтов (числа от 0 до 255 включены), потому что их память поступает в байтовых блоках.Необходимый процесс, который преобразует символы в байты, называется encoding .Таким образом, компьютеру требуется кодировка для представления символов.Любой текст, присутствующий на вашем компьютере, кодируется (до его отображения), независимо от того, отправлен ли он на терминал (который ожидает символы, закодированные определенным образом), или сохранен в файле.Чтобы быть отображенными или правильно «понятыми» (скажем, интерпретатором Python), потоки байтов декодируются в символы. Несколько кодировок (UTF-8, UTF-16, ...) определены Unicode для его списка символов (Unicode, таким образом, определяет и список символов, и кодировки для этих символов - все еще есть места, где одинвидит выражение «кодировка Unicode» как способ обращения к вездесущему UTF-8, но это неверная терминология, поскольку Unicode обеспечивает множественные кодировки).

Таким образом, компьютерам необходимо внутренне представлять символы с байтами , и они делают это с помощью двух операций:

Кодировка : символы → байты

Декодирование : байты → символы

Некоторые кодировки не могут кодировать все символы (например, ASCII), в то время как (некоторые) кодировки Unicode позволяют кодировать все символы Unicode. Кодировка также не обязательно уникальна , поскольку некоторые символы могут быть представлены либо напрямую, либо в виде комбинации (например, базового символа и ударений).

Примечаниечто концепция newline добавляет уровень сложности , поскольку он может быть представлен различными (управляющими) символами, которые зависят от операционной системы (это причина для в Pythonуниверсальный режим чтения файла новой строки ).

Теперь то, что я назвал «символом» выше, - это то, что Unicode называет « воспринимаемым пользователем символом ».Один воспринимаемый пользователем символ иногда может быть представлен в Юникоде путем объединения частей символов (базовый символ, акценты,…), найденных с разными индексами в списке Юникод, которые называются " codepoints "- эти кодовые точки могут быть объединены вместе для формирования" кластера графем ".Unicode, таким образом, приводит кПервая концепция строки, состоящая из последовательности кодовых точек Unicode, которая находится между байтовой и символьной строками и которая ближе к последней. Я назову их « Unicode-строки » (как в Python 2).

В то время как Python может печатать строки (воспринимаемых пользователем) символов, Небайтные строки Python являются по существу последовательностями кодовых точек Unicode , а не воспринимаемыми пользователем символами. Значения кодовой точки - это те, которые используются в синтаксисе строки Unicode в Python \u и \U. Их не следует путать с кодировкой символа (и не должны иметь с ним никакой связи: кодовые точки Unicode могут кодироваться различными способами).

Это имеет важное следствие: длина строки Python (Unicode) - это количество кодовых точек, которое не , всегда число воспринимаемых пользователем символов : таким образом s = "\u1100\u1161\u11a8"; print(s, "len", len(s)) (Python 3) дает 각 len 3, несмотря на то, что s имеет один воспринимаемый пользователем (корейский) символ (потому что он представлен 3 кодами - даже если это не обязательно, как показывает print("\uac01")). Однако во многих практических обстоятельствах длина строки равна количеству воспринимаемых пользователем символов, поскольку многие символы обычно хранятся в Python как единая кодовая точка Unicode.

В Python 2 строки Unicode называются… «строками Unicode» (тип unicode, буквенная форма u"…"), в то время как байтовые массивы являются «строками» (тип str, где Например, массив байтов может быть построен из строковых литералов "…"). В Python 3 строки Unicode просто называются «строками» (str тип, буквенная форма "…"), в то время как байтовые массивы являются «байтами» (bytes тип, буквальная форма b"…") .

С этими несколькими ключевыми моментами вы сможете понять большинство вопросов, связанных с кодированием!


Обычно, когда вы печатаете u"…" на терминале , вы не должны получать мусор: Python знает кодировку вашего терминала. Фактически вы можете проверить, какую кодировку ожидает терминал:

% python
Python 2.7.6 (default, Nov 15 2013, 15:20:37) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.stdout.encoding
UTF-8

Если ваши входные символы могут быть закодированы с помощью кодировки терминала, Python сделает это и отправит соответствующие байты на ваш терминал без жалоб. Затем терминал сделает все возможное, чтобы отобразить символы после декодирования входных байтов (в худшем случае шрифт терминала не содержит некоторых символов и вместо этого напечатает какой-то пробел).

Если ваши входные символы не могут быть закодированы с помощью кодировки терминала, это означает, что терминал не настроен для отображения этих символов. Python будет жаловаться (в Python с UnicodeEncodeError, поскольку символьная строка не может быть закодирована таким образом, который подходит вашему терминалу). Единственное возможное решение - использовать терминал, который может отображать символы (либо путем настройки терминала так, чтобы он принимал кодировку, которая может представлять ваши символы, либо с помощью другой программы терминала). Это важно при распространении программ, которые можно использовать в разных средах: печатаемые сообщения должны быть представлены в пользовательском терминале. Поэтому иногда лучше придерживаться строк, которые содержат только символы ASCII.

Однако, когда вы перенаправляете или перенаправляете вывод вашей программы, обычно невозможно узнать, какова входная кодировка принимающей программы, и приведенный выше код возвращает некоторую кодировку по умолчанию: Нет (Python 2.7) или UTF-8 (Python 3):

% python2.7 -c "import sys; print sys.stdout.encoding" | cat
None
% python3.4 -c "import sys; print(sys.stdout.encoding)" | cat
UTF-8

Однако кодирование stdin, stdout и stderr может быть установлено через переменную окружения PYTHONIOENCODING, если необходимо:

% PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat
UTF-8

Если печать на терминал не дает ожидаемого результата, вы можете проверить правильность кодировки UTF-8, введенной вручную; например, ваш первый символ (\u001A) не печатается, , если я не ошибаюсь .

На http://wiki.python.org/moin/PrintFails, вы можете найти решение, подобное следующему, для Python 2.x:

import codecs
import locale
import sys

# Wrap sys.stdout into a StreamWriter to allow writing unicode.
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout) 

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni

Fили Python 3, вы можете проверить один из вопросов, заданных ранее в StackOverflow.

19 голосов
/ 29 декабря 2010

Python всегда кодирует строки Unicode при записи в терминал, файл, канал и т. Д. При записи в терминал Python обычно может определить кодировку терминала и правильно его использовать.При записи в файл или канал Python по умолчанию использует кодировку «ascii», если явно не указано иное.Python может сказать, что делать при передаче данных через переменную окружения PYTHONIOENCODING.Оболочка может установить эту переменную перед перенаправлением вывода Python в файл или канал, чтобы была известна правильная кодировка.

В вашем случае вы напечатали 4 необычных символа, которые ваш терминал не поддерживал в своем шрифте.Вот несколько примеров, чтобы помочь объяснить поведение с символами, которые фактически поддерживаются моим терминалом (который использует cp437, а не UTF-8).

Пример 1

Обратите внимание, что комментарий #codingуказывает кодировку, в которой сохранен исходный файл .Я выбрал utf8, чтобы я мог поддерживать символы в источнике, которые не мог мой терминал.Кодировка перенаправляется в stderr, поэтому ее можно увидеть при перенаправлении в файл.

#coding: utf8
import sys
uni = u'αßΓπΣσµτΦΘΩδ∞φ'
print >>sys.stderr,sys.stdout.encoding
print uni

Вывод (запускается непосредственно из терминала)

cp437
αßΓπΣσµτΦΘΩδ∞φ

Python правильно определил кодировку терминала.

Вывод (перенаправлен в файл)

None
Traceback (most recent call last):
  File "C:\ex.py", line 5, in <module>
    print uni
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-13: ordinal not in range(128)

Python не может определить кодировку (нет), поэтому используется значение по умолчанию «ascii».ASCII поддерживает только преобразование первых 128 символов Unicode.

Вывод (перенаправлен в файл, PYTHONIOENCODING = cp437)

cp437

и мой выходной файл был правильным:

C:\>type out.txt
αßΓπΣσµτΦΘΩδ∞φ

Пример 2

Теперь я добавлю символ в источнике, который не поддерживается моим терминалом:

#coding: utf8
import sys
uni = u'αßΓπΣσµτΦΘΩδ∞φ马' # added Chinese character at end.
print >>sys.stderr,sys.stdout.encoding
print uni

Вывод (запускается непосредственно из терминала)

cp437
Traceback (most recent call last):
  File "C:\ex.py", line 5, in <module>
    print uni
  File "C:\Python26\lib\encodings\cp437.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode character u'\u9a6c' in position 14: character maps to <undefined>

Мой терминал не понимает этот последний китайский символ.

Вывод (выполняется напрямую, PYTHONIOENCODING = 437: заменить)

cp437
αßΓπΣσµτΦΘΩδ∞φ?

Обработчики ошибок можно указать с помощью кодировки,В этом случае неизвестные символы были заменены на ?.ignore и xmlcharrefreplace - некоторые другие опции.При использовании UTF8 (который поддерживает кодирование всех символов Unicode) замены никогда не будут сделаны, но шрифт , используемый для отображения символов, все равно должен их поддерживать.

12 голосов
/ 28 декабря 2010

Кодирование во время печати

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni.encode("utf-8")

Это потому, что при запуске сценария вручную код python кодирует его перед выводом на терминал, когда вы передаете его по конвейеру, python не кодирует сам, поэтому необходимо кодировать вручнуюпри выполнении ввода / вывода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...