2013-06-24 2 views
7

Кажется, что кодировка UTF-8 Python (codecs) интерпретирует символы Unicode 28, 29 и 30 в качестве окончаний строк. Зачем? И как я могу предотвратить это?Код кодека Python, заканчивающийся

Пример кода:

with open('unicodetest.txt', 'w') as f: 
    f.write('a'+chr(28)+'b'+chr(29)+'c'+chr(30)+'d'+chr(31)+'e') 
with open('unicodetest.txt', 'r') as f: 
    for i,l in enumerate(f): 
    print i, l 
# prints "0 abcde" with special characters in between. 

Дело в том, что он читает в одну строку, как я ожидаю, что это сделать. Теперь, когда я использую codecs, чтобы прочитать его в UTF-8, он интерпретирует его как много строк.

import codecs 
with codecs.open('unicodetest.txt', 'r', 'UTF-8') as f: 
    for i,l in enumerate(f): 
    print i, l 
# 0 a 
# 1 b 
# 2 c 
# 3 de 
# (again with the special characters after each a, b, c, d 

Символы 28 до 31 описан как «разделитель информации Четверка» через «One» (в таком порядке). Меня поражают две вещи: 1) 28-30 интерпретируются как концы линий, 2) 31 нет. Это намеренное поведение? Где можно найти определение того, какие символы интерпретируются как концы строк? Есть ли способ не интерпретировать их как концы строк?

Спасибо.

Редактировать забыл скопировать аргумент 'UTF-8' в codecs.open. Код в моем вопросе теперь исправлен.

+0

Что произойдет, если вы откроете файл в режиме '' rb''? – unutbu

+0

Не имеет значения. – Paul

+2

@Paul, вы можете ответить на свой вопрос и принять его, если хотите –

ответ

5

Это отличный вопрос.

Неважно, открываете ли вы файл с open() или codecs.open(). Первая работает в терминах байтовых строк. Последний работает в терминах строк Unicode. В Python эти behave differently.

Этот же вопрос возник как Python Issue 7643, What is a Unicode line break character?. Обсуждение и цитаты к Unicode Character Database являются увлекательными. Выпуск 7643 также дает этот краткий фрагмент кода, чтобы продемонстрировать разницы:

for s in '\x0a\x0d\x1c\x1d\x1e': 
    print u'a{}b'.format(s).splitlines(1), 'a{}b'.format(s).splitlines(1) 

Но это сводится к этому.

Чтобы определить, являются ли байты в байтовых строках разрывами строк (или пробелами), Python использует правила ASCII control characters. По этой мере байты 10 и 13 являются символами разрыва строки (и Python рассматривает байт 13, за которым следует 10 в качестве разрыва строки).

Но чтобы определить, если символы в строках Unicode являются разрывы строк, Python следует характер классификаций Unicode Character Database, документированные в UAX #44, и в UAX #14 Line Breaking Algorithm, section 5 Line Breaking Properties. По вопросу 7643, эти документы выделяют три свойства характера, которые идентифицируют символ как LineBreak для целей языка Python:

  • Общая категория Zl «Линия Separator»
  • Общая категория Zp «Пункт Separator»
  • двунаправленного класса B "Сепаратор абзаца"

Символы 28 (0x001C), 29 (0x001D) и 30 (0x001E) имеют эти свойства символов. Символ 31 (0x001F) нет. Зачем? Это вопрос для Технического комитета Юникода. Но в ASCII эти символы были известны как «File Separator», «Separator Group», «Separator записи» и «Unit Separator».Используя файл с текстовыми данными с вкладками в качестве сравнения, первые три означают, по крайней мере, такое же разделение, как и разрыв строки, в то время как четвертый, возможно, аналогичен вкладке.

Вы можете увидеть код, который фактически определяет эти три символа Unicode как разрывы строк в строках Unicode Python в Objects/unicodeobject.c. Найдите массив ascii_linebreak[]. Этот массив лежит в основе реализации unicode.splitlines(). Различный код лежит в основе str.splitlines(). Я считаю, но не прослеживал его в исходном коде Python, что enumerate() по файлу, открытому с codecs.open(), реализован в терминах unicode.splitlines().

Вы спрашиваете: «Как я могу предотвратить это?» Я не вижу никакого способа сделать splitlines() вести себя по-другому. Тем не менее, вы можете открыть файл в виде потока байтов, читать строки в байтах с str.splitlines() поведения, а затем декодировать каждую строку в UTF-8 для использования в качестве юникод строки:

with open('unicodetest.txt', 'r') as f: 
    for i,l in enumerate(f): 
    print i, l.decode('UTF-8') 
# prints "0 abcde" with special characters in between. 

Я предполагаю, что вы используете Python 2 .x, не 3.x. Мой ответ основан на Python 2.7.

+1

Спасибо. Это сложно. И спасибо за ваше решение. Имеет смысл. – Paul