Кодировка в Python 2.7

У меня есть несколько вопросов о кодировании в Python 2.7.

1. Код Python, как показано ниже,

#s = u"严"
s = u'\u4e25'
print 's is:', s
print 'len of s is:', len(s)
s1 = "a" + s
print 's1 is:', s1
print 'len of s1 is:', len(s1)

выход:

s is: 严
len of s is: 1
s1 is: a严
len of s1 is: 2

Я смущен, что почему s 1, как мог 4e25 храниться в 1 байте? Я также заметил, что USC-2 имеет длину 2 байта, а USC-4 имеет длину 4 байта, поэтому строка в кодировке Unicode sдлина 1?

2. (1) Новый файл с именем a.py с помощью блокнота ++(Windows 7) и установите кодировку файла ANSI, код в a.py как показано ниже:

# -*- encoding:utf-8 -*-
import sys
print sys.getdefaultencoding()
s = "严"
print "s:", s
print "type of s:", type(s)

выход:

ascii
s: 严
type of s: <type 'str'>

(2) Новый файл с именем b.py с помощью блокнота ++(Windows 7) и установите кодировку файла UTF-8, код в b.py как показано ниже:

# -*- encoding:gbk -*-
import sys
print sys.getdefaultencoding()
s = "严"
print "s:", s
print "type of s:", type(s)

выход:

  File "D:\pyws\code\\b.py", line 1
SyntaxError: encoding problem: utf-8

(3) изменить файл b.py как показано ниже (стиль кодирования файла UTF-8):

import sys
print sys.getdefaultencoding()
s = "严"
print "s:", s
print "type of s:", type(s)

выход:

ascii
s: 涓
type of s: <type 'str'>

(4) изменить файл a.py как показано ниже (стиль кодирования файла ANSI):

import sys
print sys.getdefaultencoding()
s = "严"
print "s:", s
print "type of s:", type(s)

выход:

  File "D:\pyws\code\a1.py", line 3
SyntaxError: Non-ASCII character '\xd1' in file D:\pyws\code\a1.py on
line 3, but no encoding declared; see http://www.python.org/peps/pep-0263.html f
or details

Почему результаты этих 4 случаев в вопросе 2 отличаются? Кто-нибудь может разобраться в деталях?

2 ответа

Решение

Ответ на вопрос 1:

В версиях Python <3.3 длина строки Unicode u'' это количество используемых кодовых единиц UTF-16 или UTF-32 (в зависимости от флагов сборки), а не количество байтов. \u4e25 является одной единицей кода, но не все символы представлены одной единицей кода, если используется UTF-16 (по умолчанию в Windows).

>>> len(u'\u42e5')
1
>>> len(u'\U00010123')
2

В Python 3.3 приведенное выше вернет 1 для обеих функций.

Также символы Unicode могут состоять из комбинированных кодовых единиц, таких как é, normalize Функция может быть использована для генерации комбинированной или разложенной формы:

>>> import unicodedata as ud
>>> ud.name(u'\xe9')
'LATIN SMALL LETTER E WITH ACUTE'
>>> ud.normalize('NFD',u'\xe9')
u'e\u0301'
>>> ud.normalize('NFC',u'e\u0301')
u'\xe9'

Таким образом, даже в Python 3.3 один отображаемый символ может иметь 1 или более единиц кода, и для согласованных ответов лучше нормализовать одну или другую форму.

Ответ на вопрос 2:

Кодировка, объявленная в верхней части файла, должна соответствовать кодировке, в которой сохранен файл. Объявление позволяет Python знать, как интерпретировать байты в файле.

Например, персонаж сохраняется как 3 байта в файле, сохраненном как UTF-8, но два байта в файле сохраняются как GBK:

>>> u'严'.encode('utf8')
'\xe4\xb8\xa5'
>>> u'严'.encode('gbk')
'\xd1\xcf'

Если вы объявляете неправильную кодировку, байты интерпретируются неправильно, и Python либо отображает неправильные символы, либо выдает исключение.

Изменить на комментарий

2 (1) - Это зависит от системы, поскольку ANSI является кодировкой локали системы по умолчанию. В моей системе это cp1252 и Notepad++ не может отображать китайский символ. Если я установлю свою системную локаль на Chinese(PRC) тогда я получаю ваши результаты на консольном терминале. Причина, по которой он работает правильно, в том случае, когда используется байтовая строка, а байты просто отправляются на терминал. Поскольку файл был закодирован в ANSI на Chinese(PRC) locale, байты, содержащиеся в байтовой строке, правильно интерпретируются Chinese(PRC) языковой терминал.

2 (2) - Файл закодирован в UTF-8, но кодировка объявлена ​​как GBK. Когда Python читает кодировку, он пытается интерпретировать файл как GBK и терпит неудачу. Вы выбрали UTF-8 в качестве кодировки, которая в Notepad++ также содержит код порядка следования байтов (BOM) в кодировке UTF-8 в качестве первого символа в файле, а кодек GBK не считывает его как действительный символ в кодировке GBK, поэтому происходит сбой в строке 1.

2 (3) - файл закодирован в UTF-8 (с спецификацией), но отсутствует объявление кодировки. Python распознает кодировку UTF-8 и использует UTF-8 в качестве кодировки, но файл находится в формате GBK. Поскольку использовалась строка байтов, байты в кодировке UTF-8 отправляются на терминал GBK, и вы получаете:

>>> u'严'.encode('utf8').decode(
'\xe4\xb8\xa5'
>>> '\xe4\xb8'.decode('gbk')
u'\u6d93'
>>> print '\xe4\xb8'.decode('gbk')
涓

В этом случае я удивлен, потому что Python игнорирует байт \xa5и, как вы увидите ниже, когда я явно неправильно декодирую, Python генерирует исключение:

>>> u'严'.encode('utf8').decode('gbk')
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
UnicodeDecodeError: 'gbk' codec can't decode byte 0xa5 in position 2: incomplete multibyte sequence

2 (4) - в этом случае кодировка ANSI (GBK), но кодировка не объявляется, и нет такой спецификации, как в UTF-8, чтобы дать Python подсказку, поэтому она принимает ASCII и не может обрабатывать GBK- закодированный символ в строке 3.

Я запутался в том, почему len s равен 1, как 4e25 может храниться в 1 байте? Я также заметил, что USC-2 имеет длину 2 байта, а USC-4 имеет длину 4 байта. Почему длина строки Unicode s равна 1?

Весь смысл Unicode-строк заключается в том, чтобы сделать это. Длина строки Unicode - это количество символов (т. Е. Кодовых точек), а не количество байтов. Количество байтов может варьироваться в зависимости от кодировки, но количество символов является абстрактным инвариантом, который не изменяется при кодировании.

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

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

  1. Вам повезет, и это будет работать, даже если вы используете конфликтующие кодировки; это то, что произошло в вашем первом случае.
  2. Это вызовет ошибку, поскольку байты в файле не соответствуют указанной кодировке; это то, что произошло в вашем втором случае.
  3. Кажется, что это работает, но выдает другие символы, потому что байты в фактической кодировке файла означают что-то другое, когда интерпретируются с указанной кодировкой. Это похоже на то, что произошло в вашем третьем случае, хотя это должно вызвать ошибку, так как этот символ не ASCII. (Под "стилем кодирования файла является UTF-8" вы имели в виду # encoding директива на этот счет в файле?)
  4. Если вы не укажете какую-либо кодировку, вы получите сообщение об ошибке, если попытаетесь использовать любые байты, которые не находятся в простом ASCII. Это то, что произошло в вашем последнем случае.

Кроме того, тип строки str во всех случаях, потому что вы не указали строку как Unicode (например, с u"..."). Указание кодировки файла не делает строки Unicode. Он просто говорит Python, как интерпретировать символы в файле.

Однако здесь возникает более серьезный вопрос: почему вы играете в эти игры с кодировками в своих примерах? Нет никаких оснований использовать # encoding маркер для указания кодировки, отличной от той, в которой фактически закодирован файл, и это гарантированно вызовет проблемы. Не делай этого. Вы должны знать, в какой кодировке находится файл, и указать ту же кодировку в # encoding маркер.

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