Это отличный вопрос.
Неважно, открываете ли вы файл с 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.
Что произойдет, если вы откроете файл в режиме '' rb''? – unutbu
Не имеет значения. – Paul
@Paul, вы можете ответить на свой вопрос и принять его, если хотите –