Codecs.open(utf-8) не читает обычный ASCII файл

У меня есть простой ASCII файл. Когда я пытаюсь открыть его с помощью codecs.open(..., "utf-8"), я не могу прочитать отдельные символы. ASCII - это подмножество UTF-8, поэтому почему codecs не может открыть такой файл в режиме UTF-8?

# test.py

import codecs

f = codecs.open("test.py", "r", "utf-8")

# ASCII is supposed to be a subset of UTF-8:
# http://www.fileformat.info/info/unicode/utf8.htm

assert len(f.read(1)) == 1 # OK
f.readline()
c = f.read(1)
print len(c)
print "'%s'" % c
assert len(c) == 1 # fails

# max% p test.py
# 63
# '
# import codecs
#
# f = codecs.open("test.py", "r", "utf-8")
#
# # ASC'
# Traceback (most recent call last):
#   File "test.py", line 15, in <module>
#     assert len(c) == 1 # fails
# AssertionError
# max%

Система:

Linux max 4.4.0-89-generic #112~14.04.1-Ubuntu SMP Tue Aug 1 22:08:32 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

Конечно, он работает с обычным open. Он также работает, если я удаляю параметр "utf-8". И что означает 63? Это похоже на середину 3-й линии. Я не понимаю.

Ответ 1

Нашел вашу проблему:

Когда передано кодирование, codecs.open возвращает StreamReaderWriter, который на самом деле является только оболочкой (не подклассом, это "состоящий из" отношения, а не наследования) StreamReader и StreamWriter. Проблема в том, что:

  • StreamReaderWriter предоставляет "обычный" read метод (то есть он принимает параметр size и что он)
  • Он делегирует внутренний метод StreamReader.read, где аргумент size - это всего лишь подсказка относительно количества прочитанных байтов, но не предел; второй аргумент chars является строгим ограничителем, но StreamReaderWriter никогда не передает этот аргумент (он его не принимает)
  • Если size намечено, но не ограничено использованием chars, если StreamReader имеет буферизованные данные, и он достаточно большой, чтобы соответствовать подсказке size StreamReader.read, слепо возвращает содержимое буфера, а не ограничивает это в любом случае основано на подсказке size (в конце концов, только chars накладывает максимальный размер возврата)

API StreamReader.read и значение size/chars для API - это единственная зарегистрированная вещь; тот факт, что codecs.open возвращает StreamReaderWriter не является договорным, а также тот факт, что StreamReaderWriter wraps StreamReader, я просто использовал ipython ?? magic для чтения исходного кода модуля codecs для проверки это поведение. Но документально или нет, что он делает (не стесняйтесь читать исходный код для StreamReaderWriter, все это на уровне Python, так что это легко).

Лучшим решением является переход на io.open, который быстрее и правильнее в каждом стандартном случае (codecs.open поддерживает кодеки weirdo, которые не конвертируются между bytes [Py2 str] и str [Py2 unicode], а, дескриптор str в str или bytes в bytes кодировки, но это невероятно ограниченный случай использования; большую часть времени вы конвертируете между bytes и str). Все, что вам нужно сделать, это импортировать io вместо codecs и изменить строку codecs.open на:

f = io.open("test.py", encoding="utf-8")

Остальная часть вашего кода может оставаться неизменной (и, скорее всего, будет работать быстрее для загрузки).

В качестве альтернативы вы можете явно обходить StreamReaderWriter, чтобы получить метод StreamReader read и передать предельный аргумент напрямую, например. изменить:

c = f.read(1)

в

# Pass second, character limiting argument after size hint
c = f.reader.read(6, 1)  # 6 is sort of arbitrary; should ensure a full char read in one go

Я подозреваю Ошибка Python # 8260, которая охватывает перемежающиеся readline и read на codecs.open созданные объекты файлов, применяется здесь, официально, оно "исправлено", но если вы прочтете комментарии, исправление не было полным (и может быть невозможно завершить данный документированный API); произвольно странные комбинации read и readline смогут сломать его.

Опять же, просто используйте io.open; если вы на Python 2.6 или выше, он доступен, и он просто лучше.