Следующий код работает в Python 3:

people = [u'Nicholas Gyeney', u'Andr\xe9']
writers = ", ".join(people)
print(writers)
print("Writers: {}".format(writers))

И выдает следующий вывод:

Nicholas Gyeney, André  
Writers: Nicholas Gyeney, André

В Python 2.7, однако, я получаю следующую ошибку:

Traceback (most recent call last):
  File "python", line 4, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' 
in position 21: ordinal not in range(128)

Я могу исправить эту ошибку, изменив ", ".join(people) на ", ".join(people).encode('utf-8'), но если я это сделаю, вывод в Python 3 изменится на:

b'Nicholas Gyeney, Andr\xc3\xa9'  
Writers: b'Nicholas Gyeney, Andr\xc3\xa9'

Поэтому я попытался использовать следующий код:

if sys.version_info < (3, 0):
    reload(sys)
    sys.setdefaultencoding('utf-8')

people = [u'Nicholas Gyeney', u'Andr\xe9']
writers = ", ".join(people)
print(writers)
print("Writers: {}".format(writers))

Что заставляет мой код работать во всех версиях Python. Но я читал, что использование setdefaultencoding не рекомендуется.

Какой лучший подход для решения этой проблемы?

6
B Faley 9 Янв 2017 в 10:02

4 ответа

Лучший ответ

Сначала мы предполагаем, что вы хотите поддерживать версии Python 2.7 и 3.5 (2.6 и 3.0 до 3.2 обрабатываются немного по-другому).

Как вы уже прочитали, setdefaultencoding не рекомендуется и фактически не требуется в вашем случае.

Для написания кроссплатформенного кода, работающего с текстом Unicode, обычно нужно указывать строковое кодирование только в нескольких местах:

  1. Вверху вашего скрипта, ниже шебанга с # -*- coding: utf-8 -*- (только если у вас есть строковые литералы с текстом в кодировке Юникод)
  2. Когда вы читаете входные данные (например, из текстового файла или базы данных)
  3. Когда вы выводите данные (снова из текстового файла или базы данных)
  4. Когда вы определяете строковый литерал в коде

Вот как я изменил ваш пример, следуя этим правилам:

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

people = ['Nicholas Gyeney', 'André']
writers = ", ".join(people)
print(writers)
print("Writers: {}".format(writers))

print(type(writers))
print(len(writers))

Какие выводы:

<type 'str'>
23

Вот что изменилось:

  • Указанная кодировка файла вверху файла
  • Заменено \xe9 фактическим символом Юникода (é)
  • Удалены u префиксы

Это прекрасно работает в Python 2.7.12 и 3.5.2.

Но имейте в виду, что удаление префиксов u заставит python использовать обычный str тип вместо unicode (см. Вывод print(type(writers))). В случае utf-8 он работает в большинстве мест, как если бы это была строка в кодировке Unicode, но при проверке длины текста будет возвращено неправильное значение. В этом примере len возвращает 23, где фактическое количество символов равно 22. Это связано с тем, что базовым типом является str, который считает каждый байт символом, а символ é должен фактически составлять два байта.

Другими словами, это работает при точном выводе данных (как в вашем примере), но не при необходимости манипулирования строками в тексте. В этом случае вам все еще нужно использовать префикс u или преобразовать данные в тип unicode явно, до манипуляции со строками.

Так что, если бы не ваш простой пример, было бы лучше использовать префикс u. Это нужно в двух местах:

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

people = [u'Nicholas Gyeney', u'André']
writers = ", ".join(people)
print(writers)
print(u"Writers: {}".format(writers))

print(type(writers))
print(len(writers))

Какие выводы:

<type 'unicode'>
22

Примечание. Префикс u был удален в Python 3.0, а затем снова введен в Python 3.3 для обратной совместимости.

Подробное объяснение всех тонкостей работы с текстом Unicode в Python 2 доступно в официальной документации: Python 2 - Юникод HOWTO.

Вот выдержка из специального комментария, задающего кодировку файла:

Python поддерживает написание литералов Unicode в любой кодировке, но вы должны объявить используемую кодировку. Это делается путем добавления специального комментария в качестве первой или второй строки исходного файла:

#!/usr/bin/env python
# -*- coding: latin-1 -*-

u = u'abcdé' print ord(u[-1])

Синтаксис основан на нотации Emacs для указания переменных локально для файла. Emacs поддерживает много разных переменных, но Python поддерживает только coding. Символы -*- указывают Emacs, что комментарий особенный; они не имеют никакого значения для Python, но являются условность. Python ищет coding: name или coding=name в комментарий.

Если вы не включите такой комментарий, кодировка по умолчанию будет ASCII.

Если вам достанется книга «Изучение Python, 5-е издание», я призываю вас прочитать главу 37 «Юникод и байтовые строки» в части VIII. Расширенные темы. Он содержит подробное объяснение работы с текстом Unicode в обоих поколениях Python.

Стоит отметить еще одну деталь: format всегда возвращает строку ascii, если строка формата была ascii, независимо от того, были ли аргументы в unicode.

В противоположность этому, форматирование старого стиля с % возвращает строку unicode, если какой-либо из аргументов равен unicode. Так что вместо того, чтобы писать это

print(u"Writers: {}".format(writers))

Вы могли бы написать это, которое не только короче и красивее, но работает как в Python 2, так и в 3:

print("Writers: %s" % writers)
8
quasoft 9 Янв 2017 в 11:08

Вы можете указать префикс Unicode при форматировании:

print(u"Writers: {}".format(writers))

Это решает проблему, но вы заваливаете свой скрипт на Python 3 ненужными u'' префиксами.

Вы могли бы также from __future__ import unicode_literals после проверки версии, но я бы не стал этого делать, с ним обычно работать сложнее, и он считается устаревшим, поскольку префикс u'' выполняет свою работу в достаточной степени.

3
Dimitris Fasarakis Hilliard 9 Янв 2017 в 07:25

В Python2 вы должны использовать строки Unicode для join и print:

people = [u'Nicholas Gyeney', u'Andr\xe9']
writers = u", ".join(people)
print(writers)
print(u"Writers: {}".format(writers))
2
Fomalhaut 9 Янв 2017 в 07:11

Ответ заключается в том, чтобы сделать все Unicode:

# -*- coding: utf-8 -*-
people = [u'Nicholas Gyeney', u'André']
writers = u", ".join(people)
print(writers)
print(u"Writers: {}".format(writers))
0
Stephen Rauch 9 Янв 2017 в 07:15