Grava no arquivo UTF-8 em Python

204

Estou realmente confuso com o codecs.open function. Quando eu faço:

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

Isso me dá o erro

UnicodeDecodeError: o codec 'ascii' não pode decodificar o byte 0xef na posição 0: ordinal fora do intervalo (128)

Se eu fizer:

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

Funciona bem.

A pergunta é por que o primeiro método falha? E como insiro o bom?

Se o segundo método é a maneira correta de fazê-lo, qual o sentido de usar codecs.open(filename, "w", "utf-8")?

John Jiang
fonte
54
Não use uma lista técnica no UTF-8. Por favor.
tchrist
7
@tchrist Huh? Por que não?
Salman von Abbas
8
A BOM @SalmanPK não é necessária no UTF-8 e apenas adiciona complexidade (por exemplo, você não pode concatenar arquivos da BOM e resultar com texto válido). Veja estas perguntas e respostas ; não perca o grande comentário em Q
Alois Mahdal

Respostas:

271

Acredito que o problema é que codecs.BOM_UTF8é uma sequência de bytes, não uma sequência Unicode. Eu suspeito que o manipulador de arquivos está tentando adivinhar o que você realmente quer dizer com base em "Eu devo escrever Unicode como texto codificado em UTF-8, mas você me forneceu uma string de bytes!"

Tente escrever a sequência Unicode para a marca de ordem dos bytes (ou seja, Unicode U + FEFF) diretamente, para que o arquivo codifique apenas como UTF-8:

import codecs

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

(Isso parece dar a resposta certa - um arquivo com bytes EF BB BF.)

EDIT: A sugestão de S. Lott de usar "utf-8-sig" como a codificação é melhor do que escrever explicitamente a BOM, mas deixarei esta resposta aqui, pois explica o que estava errado antes.

Jon Skeet
fonte
Aviso: abrir e abrir não é o mesmo. Se você "de codecs import open", NÃO será o mesmo que você simplesmente digitar "open".
Apache
2
você também pode usar codecs.open ('test.txt', 'w', 'utf-8-sig')
encerrou o beta
1
Estou recebendo "TypeError: é necessário um número inteiro (obteve o tipo str)". Não entendo o que estamos fazendo aqui. Alguém pode ajudar por favor? Eu preciso anexar uma string (parágrafo) a um arquivo de texto. Preciso converter isso em um número inteiro antes de escrever?
Mugen
@Mugen: O código exato que eu escrevi funciona bem, tanto quanto eu posso ver. Sugiro que você faça uma nova pergunta mostrando exatamente qual código você possui e onde o erro ocorre.
Jon tiro ao prato
@Mugen você precisa ligar em codecs.openvez de apenas #open
northben 15/05
179

Leia o seguinte: http://docs.python.org/library/codecs.html#module-encodings.utf_8_sig

Faça isso

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

O arquivo resultante é UTF-8 com a lista técnica esperada.

S.Lott
fonte
2
Obrigado. Isso funcionou (Windows 7 x64, Python 2.7.5 x64). Esta solução funciona bem quando você abre o arquivo no modo "a" (anexar).
Mohamad Fakih 23/08
Isso não funcionou para mim, Python 3 no Windows. Eu tive que fazer isso com open (file_name, 'wb') como bomfile: bomfile.write (codecs.BOM_UTF8) e reabra o arquivo para acrescentar.
Dustin Andrews
Talvez adicionar temp.close()?
user2905353 4/01
2
@ user2905353: não necessário; isso é tratado pelo gerenciamento de contexto de open.
matheburg 28/03
11

O @ S-Lott fornece o procedimento correto, mas, expandindo os problemas de Unicode , o intérprete Python pode fornecer mais informações.

Jon Skeet está certo (incomum) sobre o codecsmódulo - ele contém seqüências de bytes:

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

Escolhendo outro nit, ele BOMtem um nome Unicode padrão e pode ser inserido como:

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

Também é acessível via unicodedata:

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

Eu uso o comando file * nix para converter um arquivo charset desconhecido em um arquivo 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()
Ricardo
fonte
1
Use em # coding: utf8vez do # -*- coding: utf-8 -*-que é muito mais fácil de lembrar.
show0k