Почему не работает это преобразование в utf8? - PullRequest
13 голосов
/ 23 августа 2011

У меня есть команда подпроцесса, которая выводит некоторые символы, такие как '\ xf1'.Я пытаюсь декодировать его как utf8, но я получаю сообщение об ошибке.

s = '\xf1'
s.decode('utf-8')

Вышеуказанные значения:

UnicodeDecodeError: 'utf8' codec can't decode byte 0xf1 in position 0: unexpected end of data

Это работает, когда я использую 'latin-1', но не должен 'Тоже работает?Насколько я понимаю, что latin1 является подмножеством utf8.

Я что-то здесь упускаю?

EDIT:

print s # ñ
repr(s) # returns "'\\xa9'"

Ответы [ 4 ]

8 голосов
/ 23 августа 2011

Вы перепутали Юникод с UTF-8.Latin-1 является подмножеством Unicode, но это не подмножество UTF-8. Избегайте чумы, когда-либо думающей об отдельных единицах кода. Просто используйте кодовые точки.Не думай о UTF-8.Вместо этого подумайте о Юникоде.Это то, что вас смущает.

Исходный код для демонстрационной программы

Использовать Unicode в Python очень просто.Особенно это касается Python 3 и широких сборок, единственный способ, которым я использую Python, но вы все равно можете использовать устаревший Python 2 в узкой сборке, если вы осторожны с соблюдением UTF-8.

Для этого всегда правильно указывайте кодировку исходного кода и кодировку вывода в формате UTF-8.Теперь перестаньте думать о UTF-что-нибудь и используйте только литералы UTF-8, номера логических кодовых точек или символьные имена символов в вашей программе Python.

Вот исходный код с номерами строк:

% cat -n /tmp/py
     1  #!/usr/bin/env python3.2
     2  # -*- coding: UTF-8 -*-
     3  
     4  from __future__ import unicode_literals
     5  from __future__ import print_function
     6  
     7  import sys
     8  import os
     9  import re
    10  
    11  if not (("PYTHONIOENCODING" in os.environ)
    12              and
    13          re.search("^utf-?8$", os.environ["PYTHONIOENCODING"], re.I)):
    14      sys.stderr.write(sys.argv[0] + ": Please set your PYTHONIOENCODING envariable to utf8\n")
    15      sys.exit(1)
    16  
    17  print('1a: el ni\xF1o')
    18  print('2a: el nin\u0303o')
    19  
    20  print('1a: el niño')
    21  print('2b: el niño')
    22  
    23  print('1c: el ni\N{LATIN SMALL LETTER N WITH TILDE}o')
    24  print('2c: el nin\N{COMBINING TILDE}o')

А вот функции печати с символами, отличными от ASCII , заключенные в кавычки с использованием записи \x{⋯}:

% grep -n ^print /tmp/py | uniquote -x
17:print('1a: el ni\xF1o')
18:print('2a: el nin\u0303o')
20:print('1b: el ni\x{F1}o')
21:print('2b: el nin\x{303}o')
23:print('1c: el ni\N{LATIN SMALL LETTER N WITH TILDE}o')
24:print('2c: el nin\N{COMBINING TILDE}o')

Образцы прогонов демонстрационной программы

Вот пример прогона этой программы, который показывает три различных способа (a, b и c) ее выполнения: первый набор в качестве литералов в вашемисходный код (который будет подвергаться преобразованиям NFC StackOverflow и поэтому не может быть доверенным !!!) и вторые два набора с числовыми кодовыми точками Unicode и с символьными именами Unicode соответственно,снова uniquoted , чтобы вы могли видеть, что на самом деле:

% python /tmp/py
1a: el niño
2a: el niño
1b: el niño
2b: el niño
1c: el niño
2c: el niño

% python /tmp/py | uniquote -x
1a: el ni\x{F1}o
2a: el nin\x{303}o
1b: el ni\x{F1}o
2b: el nin\x{303}o
1c: el ni\x{F1}o
2c: el nin\x{303}o

% python /tmp/py | uniquote -v
1a: el ni\N{LATIN SMALL LETTER N WITH TILDE}o
2a: el nin\N{COMBINING TILDE}o
1b: el ni\N{LATIN SMALL LETTER N WITH TILDE}o
2b: el nin\N{COMBINING TILDE}o
1c: el ni\N{LATIN SMALL LETTER N WITH TILDE}o
2c: el nin\N{COMBINING TILDE}o

Мне действительно не нравится смотреть на двоичный файл, но вот как это выглядит как двоичные байты:

% python /tmp/py | uniquote -b
1a: el ni\xC3\xB1o
2a: el nin\xCC\x83o
1b: el ni\xC3\xB1o
2b: el nin\xCC\x83o
1c: el ni\xC3\xB1o
2c: el nin\xCC\x83o

Мораль истории

Даже когда вы используете источник UTF-8, вы должны думать и использовать только номера логических кодовых точек Unicode (или символьные именованные символы), а не отдельные 8-битные кодовые единицы.TS, которые лежат в основе последовательного представления UTF-8 (или в этом отношении UTF-16).Крайне редко нужны кодовые блоки вместо кодовых точек, и это просто сбивает вас с толку.

Вы также получите более надежное поведение, если будете использовать широкую сборку Python3, чем с альтернативами этим вариантам,но это вопрос UTF-32, а не UTF-8.С UTF-32 и UTF-8 легко работать, если вы просто плывете по течению.

4 голосов
/ 23 августа 2011

UTF-8 не является подмножеством Latin-1. UTF-8 кодирует ASCII одинаковыми байтами. Для всех остальных кодовых точек это все несколько байтов.

Проще говоря, \ xf1 не является допустимым UTF-8, как говорит Python. «Неожиданный конец ввода» указывает, что этот байт отмечает начало многобайтовой последовательности, которая не предоставлена.

Я рекомендую вам прочитать на UTF-8 .

1 голос
/ 23 августа 2011

Это первый байт многобайтовой последовательности в UTF-8, поэтому он сам по себе недопустим.

Фактически, это первый байт 4-байтовой последовательности.

Bits Last code point Byte 1   Byte 2   Byte 3   Byte 4   Byte 5   Byte 6
21   U+1FFFFF        11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

См. здесь для получения дополнительной информации.

1 голос
/ 23 августа 2011

Насколько я понимаю, latin1 - это подмножество utf8.

Неправильно. Латиница-1, также известная как ISO 8859-1 (а иногда ошибочно, например Windows-1252 ), не является частью UTF-8. ASCII, с другой стороны, является подмножеством UTF-8. Строки ASCII являются допустимыми строками UTF-8, но обобщенные строки Windows-1252 или ISO 8859-1 не являются действительными строками UTF-8, поэтому s.decode('UTF-8') вызывает UnicodeDecodeError.

...