Запись в файл UTF-8 на Python


204

Я действительно запутался с codecs.open function. Когда я делаю:

file = codecs.open("temp", "w", "utf-8")
file.write(codecs.BOM_UTF8)
file.close()

Это дает мне ошибку

UnicodeDecodeError: кодек «ascii» не может декодировать байт 0xef в позиции 0: порядковый номер не в диапазоне (128)

Если я сделаю:

file = open("temp", "w")
file.write(codecs.BOM_UTF8)
file.close()

Работает нормально.

Вопрос в том, почему первый метод терпит неудачу? И как мне вставить бом?

Если второй метод является правильным способом, какой смысл использовать codecs.open(filename, "w", "utf-8")?


54
Не используйте спецификацию в UTF-8. Пожалуйста.
tchrist

7
@ tchrist А? Почему нет?
Салман фон Аббас

8
@SalmanPK BOM не требуется в UTF-8 и только добавляет сложности (например, вы не можете просто объединить файлы BOM и получить правильный текст). Смотрите этот Q & A ; не пропустите большой комментарий под Q
Alois Mahdal

Ответы:


271

Я считаю, что проблема в том, что codecs.BOM_UTF8это строка байтов, а не строка Юникода. Я подозреваю, что обработчик файлов пытается угадать, что вы на самом деле имеете в виду, основываясь на «Я должен писать Unicode как текст в кодировке UTF-8, но вы дали мне строку байтов!»

Попробуйте написать строку Unicode для метки порядка байтов (т.е. Unicode U + FEFF) напрямую, чтобы файл просто кодировал ее как UTF-8:

import codecs

file = codecs.open("lol", "w", "utf-8")
file.write(u'\ufeff')
file.close()

(Это, кажется, дает правильный ответ - файл с байтами EF BB BF.)

РЕДАКТИРОВАТЬ: Предложение С. Лотта об использовании «utf-8-sig» в качестве кодировки лучше, чем явное написание собственной спецификации, но я оставлю этот ответ здесь, поскольку он объясняет, что происходило раньше.


Предупреждение: открыть и открыть не одно и то же. Если вы делаете «из импорта кодеков открытым», оно НЕ будет таким же, как если бы вы просто набирали «открыть».
Apache

2
Вы также можете использовать codecs.open ('test.txt', 'w', 'utf-8-sig')
бета-закрыто

1
Я получаю "TypeError: требуется целое число (получил тип str)". Я не понимаю, что мы здесь делаем. Может кто-нибудь помочь, пожалуйста? Мне нужно добавить строку (абзац) в текстовый файл. Нужно ли конвертировать это в целое число, прежде чем писать?
Мюген

@ Mugen: Точный код, который я написал, работает хорошо, насколько я вижу. Я предлагаю вам задать новый вопрос, показывающий , какой именно код у вас есть, и где происходит ошибка.
Джон Скит

@ Муген, тебе нужно звонить, codecs.openа не простоopen
Нортбен

179

Прочитайте следующее: http://docs.python.org/library/codecs.html#module-encodings.utf_8_sig

Сделай это

with codecs.open("test_output", "w", "utf-8-sig") as temp:
    temp.write("hi mom\n")
    temp.write(u"This has ♭")

В результате получается файл UTF-8 с ожидаемой спецификацией.


2
Спасибо. Это сработало (Windows 7 x64, Python 2.7.5 x64). Это решение хорошо работает, когда вы открываете файл в режиме «а» (добавление).
Мохамад Факих

Это не сработало для меня, Python 3 на Windows. Вместо этого мне пришлось сделать это с open (file_name, 'wb') как bomfile: bomfile.write (codecs.BOM_UTF8), а затем заново открыть файл для добавления.
Дастин Эндрюс

Может добавить temp.close()?
user2905353

2
@ user2905353: не требуется; это обрабатывается управления контекстом из open.
Матебург

11

@ S-Lott дает правильную процедуру, но, углубляясь в проблемы Unicode , интерпретатор Python может предоставить больше информации.

Джон Скит прав (необычен) в отношении codecsмодуля - он содержит байтовые строки:

>>> import codecs
>>> codecs.BOM
'\xff\xfe'
>>> codecs.BOM_UTF8
'\xef\xbb\xbf'
>>> 

Выбирая другую гадость, она BOMимеет стандартное имя Unicode , и его можно ввести как:

>>> bom= u"\N{ZERO WIDTH NO-BREAK SPACE}"
>>> bom
u'\ufeff'

Это также доступно через unicodedata:

>>> import unicodedata
>>> unicodedata.lookup('ZERO WIDTH NO-BREAK SPACE')
u'\ufeff'
>>> 

8

Я использую команду file * nix для преобразования неизвестного файла кодировки в файл utf-8

# -*- encoding: utf-8 -*-

# converting a unknown formatting file in utf-8

import codecs
import commands

file_location = "jumper.sub"
file_encoding = commands.getoutput('file -b --mime-encoding %s' % file_location)

file_stream = codecs.open(file_location, 'r', file_encoding)
file_output = codecs.open(file_location+"b", 'w', 'utf-8')

for l in file_stream:
    file_output.write(l)

file_stream.close()
file_output.close()

1
Используйте # coding: utf8вместо # -*- coding: utf-8 -*-которого гораздо легче запомнить.
show0k
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.