UnicodeEncodeError, если вывод по конвейеру передается на wc -l

При запуске кода:

#! /usr/bin/env python
# -*- coding: UTF-8 -*-

import xml.etree.ElementTree as ET
print ET.fromstring('<?xml version = "1.0" encoding = "UTF-8" standalone = "yes"?><root><road>vägen</road></root>').find('road').text 

Производит ожидаемый вывод vägen, однако, если передать это по конвейеру wc -l, я получаю UnicodeEncodeError, например. (TEerr.py содержит фрагмент кода, приведенный выше):

:~> ETerr.py | wc -l
Traceback (most recent call last):
  File "./ETerr.py", line 5, in <module>
    print ET.fromstring('<?xml version = "1.0" encoding = "UTF-8" standalone = "yes"?><root><road>vägen</road></root>').find('road').text 
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 1: ordinal not in range(128)
0
:~> 

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

Обратите внимание, что приведенный выше фрагмент кода просто настроен для демонстрации проблемы с минимальным количеством кода, в реальном сценарии, где мне нужно решить проблему, xml извлекается с помощью urllib, поэтому я мало могу контролировать его формат.

Попробуйте разбить это на части: одна хранит xml в виде строки, одна шляпа выполняет анализ ET, другая выполняет поиск и, наконец, третья выполняет печать. наверное - последний из них, который терпит неудачу, но знать наверняка полезно.

abarnert 09.04.2018 16:56

В любом случае: когда вы печатаете строку Unicode в Python 2, она использует кодировку по умолчанию. Вероятно, он угадывает значение по умолчанию UTF-8 или Latin1, когда stdout является терминалом, и ASCII, когда это канал. В обоих случаях попробуйте распечатать sys.getdefaultencoding(). Если это проблема, покажите нам, какие у вас переменные среды с префиксом LOCALE и LC_.

abarnert 09.04.2018 17:00

добавить .encode('utf-8') в конец оператора печати

eagle 09.04.2018 17:02

Если вы знаете, что ваш терминал и инструменты ожидают определенной кодировки, и вы просто хотите заставить свой код использовать эту кодировку, несмотря ни на что, не заботясь о переносимости на другие системы, вы можете print u.encode(‘utf-8’). Но было бы лучше диагностировать и решать актуальные проблемы с конфигурацией.

abarnert 09.04.2018 17:04

Кроме того, есть ли причина для этого использовать Python 2? Поскольку кодировка stdio - одна из вещей, улучшенных в Python 3 и улучшенных еще несколько раз в Python 3.7, и это, скорее всего, вообще не появится.

abarnert 09.04.2018 17:06

Это чистая проблема Python, как показано в трассировке стека, команда в конвейере теперь ничего не говорит о следующей команде и не заботится об этом.

Luis Muñoz 09.04.2018 17:13

@abarnert, да, это утверждение print не работает. sys.getdefaultencoding() дает ascii независимо от того, подключен ли выход по трубопроводу или нет

cpaitor 09.04.2018 17:17

@eagle, конечно, это решает проблему, но на самом деле не объясняет, почему поведение отличается, когда вывод передается по конвейеру или нет

cpaitor 09.04.2018 17:19

Еще одно исключение: я предполагаю, что вы работаете в системе POSIX (Linux, Mac или другой * BSD), а не в Windows; я прав? (Даже с Cygwin Python все становится сложнее в Windows, поэтому я хочу убедиться, что мы можем проигнорировать это здесь.)

abarnert 09.04.2018 19:08
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
9
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, позвольте мне указать, что это не проблема в Python 3, и ее исправление на самом деле является одной из причин, по которым вообще стоило изменить язык, нарушающий совместимость. Но я предполагаю, что у вас есть веская причина для использования Python 2, и вы не можете просто обновить его.


Ближайшая причина здесь (при условии, что вы используете Python 2.7 на платформе POSIX - все может быть сложнее в старых версиях 2.x и Windows) - это значение sys.stdout.encoding. Когда вы запускаете интерпретатор, он выполняет эквивалент этого псевдокода:

if isatty(stdoutfd):
    sys.stdout.encoding = parse_locale(os.environ('LC_CTYPE'))
else:
    sys.stdout.encoding = None

И каждый раз, когда вы записываете write в файл, включая sys.stdout, в том числе неявно из оператора print, он делает что-то вроде этого:

if isinstance(s, unicode):
    if self.encoding:
        s = s.encode(self.encoding)
    else:
        s = s.encode(sys.getdefaultencoding())

Фактический код выполняет стандартные функции POSIX, ища резервные варианты, такие как LANG, и жестко кодирует откат к UTF-8 в некоторых случаях для Mac OS X и т. д., Но это достаточно близко.


Это очень редко задокументировано в разделе file.encoding:

The encoding that this file uses. When Unicode strings are written to a file, they will be converted to byte strings using this encoding. In addition, when the file is connected to a terminal, the attribute gives the encoding that the terminal is likely to use (that information might be incorrect if the user has misconfigured the terminal). The attribute is read-only and may not be present on all file-like objects. It may also be None, in which case the file uses the system default encoding for converting Unicode strings.


Чтобы убедиться, что это ваша проблема, попробуйте следующее:

$ python -c 'print __import__("sys").stdout.encoding'
UTF-8
$ python -c 'print __import__("sys").stdout.encoding' | cat
None

Чтобы быть более уверенным, что проблема в этом:

$ PYTHONIOENCODING=Latin-1 python -c 'print __import__("sys").stdout.encoding'
Latin-1
$ PYTHONIOENCODING=Latin-1 python -c 'print __import__("sys").stdout.encoding' | cat
Latin-1

Итак, как это исправить?

Что ж, очевидный способ - перейти на Python 3.6, где вы получите UTF-8 в обоих случаях, но я предполагаю, что есть причина, по которой вы используете Python 2.7 и не можете легко его изменить.

Правильное решение на самом деле довольно сложно. Но если вам нужно быстрое и грязное решение, которое работает для вашей системы и для большинства современных систем Linux и Mac со стандартными настройками Python 2.7 (даже если это может быть катастрофически неправильным для старых систем Linux, старых версий Python 2.x и странных настроек) , вы также можете:

  • Установите переменную среды PYTHONIOENCODING, чтобы переопределить обнаружение и принудительно использовать UTF-8. Возможно, стоит установить это в вашем profile или аналогичном, если вы знаете, что каждый терминал и каждый инструмент, который вы когда-либо собираетесь использовать из этой учетной записи, - это UTF-8, хотя это ужасная идея, если это не так.
  • Проверьте sys.stdout.encoding и оберните его кодировкой 'UTF-8', если это None.
  • Явно .encode('UTF-8') на всем, что вы печатаете.

Вы правы, я должен был добавить, что это не проблема в python3 и что проблема проявляется на платформе POSIX. Интересно, однако, какой смысл устанавливать кодировку stdout в зависимости от того, является ли это tty-подобным устройством или нет?

cpaitor 09.04.2018 20:51

@cpaitor Я предполагаю, что исходное обоснование имело смысл для большинства систем Unix 90-х годов (где языковой стандарт терминала вполне мог быть полностью отделен от того, что ожидалось в большинстве текстовых файлов - если это вообще было что-то полезное с самого начала), и что все попытки придумать что-то более подходящее для современного Linux имели слишком много проблем с обратной совместимостью, чтобы реализовать их до версии 3.0, но я не знаю наверняка. (OS X упростила это, просто указав, что терминал, все инструменты пользовательского пространства и все текстовые файлы имеют кодировку UTF-8, точка…)

abarnert 09.04.2018 21:57

@cpaitor Также: дистрибутивы linux постепенно преобразовывались в "все по умолчанию UTF-8" примерно в то же время, когда они начинали миграцию на Python 3. Так что, вероятно, имело смысл потратить больше энергии на то, чтобы убедиться, что Python 3 работает в их дистрибутиве из коробки, чем настраивать пользовательские конфигурации Python 2 для их пакетов дистрибутива. (Тем более, что по крайней мере у Canonical и Redhat были штатные разработчики Python, работающие над миграцией Python 3.)

abarnert 09.04.2018 22:14

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