Leitura e gravação Unicode (UTF-8) em arquivos em Python

330

Estou tendo alguma falha cerebral ao entender a leitura e gravação de texto em um arquivo (Python 2.4).

# The string, which has an a-acute in it.
ss = u'Capit\xe1n'
ss8 = ss.encode('utf8')
repr(ss), repr(ss8)

("u'Capit \ xe1n '", "' Capit \ xc3 \ xa1n '")

print ss, ss8
print >> open('f1','w'), ss8

>>> file('f1').read()
'Capit\xc3\xa1n\n'

Então, digito no Capit\xc3\xa1nmeu editor favorito, no arquivo f2.

Então:

>>> open('f1').read()
'Capit\xc3\xa1n\n'
>>> open('f2').read()
'Capit\\xc3\\xa1n\n'
>>> open('f1').read().decode('utf8')
u'Capit\xe1n\n'
>>> open('f2').read().decode('utf8')
u'Capit\\xc3\\xa1n\n'

O que não estou entendendo aqui? Claramente, há um pouco de mágica vital (ou bom senso) que estou perdendo. O que digita em arquivos de texto para obter conversões adequadas?

O que realmente não estou conseguindo entender aqui é qual é o objetivo da representação UTF-8, se você não conseguir que o Python realmente o reconheça, quando vem de fora. Talvez eu devesse apenas JSON despejar a string e usá-la, pois ela tem uma representação asciiable! Mais ao ponto, existe uma representação ASCII desse objeto Unicode que o Python reconhecerá e decodificará quando vier de um arquivo? Se sim, como faço para obtê-lo?

>>> print simplejson.dumps(ss)
'"Capit\u00e1n"'
>>> print >> file('f3','w'), simplejson.dumps(ss)
>>> simplejson.load(open('f3'))
u'Capit\xe1n'
Gregg Lind
fonte

Respostas:

110

Na notação

u'Capit\xe1n\n'

o "\ xe1" representa apenas um byte. "\ x" indica que "e1" está em hexadecimal. Quando você escreve

Capit\xc3\xa1n

no seu arquivo você tem "\ xc3" nele. Esses são 4 bytes e no seu código você lê todos eles. Você pode ver isso quando os exibe:

>>> open('f2').read()
'Capit\\xc3\\xa1n\n'

Você pode ver que a barra invertida é escapada por uma barra invertida. Então você tem quatro bytes na sua string: "\", "x", "c" e "3".

Editar:

Como outros apontaram em suas respostas, você deve apenas digitar os caracteres no editor e seu editor deve manipular a conversão para UTF-8 e salvá-lo.

Se você realmente possui uma string nesse formato, pode usar o string_escapecodec para decodificá-la em uma string normal:

In [15]: print 'Capit\\xc3\\xa1n\n'.decode('string_escape')
Capitán

O resultado é uma string codificada em UTF-8, em que o caractere acentuado é representado pelos dois bytes que foram gravados \\xc3\\xa1 na sequência original. Se você deseja ter uma cadeia unicode, decodifique novamente com UTF-8.

Para sua edição: você não possui UTF-8 no seu arquivo. Para realmente ver como seria:

s = u'Capit\xe1n\n'
sutf8 = s.encode('UTF-8')
open('utf-8.out', 'w').write(sutf8)

Compare o conteúdo do arquivo utf-8.outcom o conteúdo que você salvou com seu editor.


fonte
Então, qual é o sentido do formato codificado utf-8 se o python puder ler os arquivos usando-o? Em outras palavras, existe alguma representação ascii que o python leia em \ xc3 como 1 byte?
Gregg Lind
4
A resposta para sua pergunta "Então, qual é o sentido ..." é "Mu". (já que o Python pode ler arquivos codificados em UTF-8). Para sua segunda pergunta: \ xc3 não faz parte do conjunto ASCII. Talvez você queira dizer "codificação de 8 bits". Você está confuso sobre Unicode e codificações; está tudo bem, muitos são.
Tbot
8
Tente ler isso como uma cartilha: joelonsoftware.com/articles/Unicode.html
tzot
nota: u'\xe1'é um ponto de código Unicode U+00e1que pode ser representado usando 1 ou mais bytes, dependendo da codificação de caracteres (são 2 bytes em utf-8). b'\xe1'é um byte (um número 225), que se qualquer carta pode representar depende carácter codificação usada para o descodificar, por exemplo, é б( U+0431) em CP1251, с( U+0441) em CP866, etc.
jfs
11
É incrível quantos codificadores britânicos dizem "apenas use ascii" e depois não percebem que o sinal de libra não é esse. A maioria não sabe que ascii! = Página de código local (ou seja, latin1).
Danny Staple
712

Em vez de mexer nos métodos de codificação e decodificação, acho mais fácil especificar a codificação ao abrir o arquivo. O iomódulo (adicionado no Python 2.6) fornece umio.open função que possui um parâmetro de codificação.

Use o método aberto do iomódulo.

>>>import io
>>>f = io.open("test", mode="r", encoding="utf-8")

Depois de chamar a função read () de f, um objeto Unicode codificado é retornado.

>>>f.read()
u'Capit\xe1l\n\n'

Observe que no Python 3, a io.openfunção é um alias para a openfunção interna. A função aberta interna suporta apenas o argumento de codificação no Python 3, não no Python 2.

Editar: Anteriormente, esta resposta recomendava o módulo de codecs . O módulo de codecs pode causar problemas ao misturar read()ereadline() , portanto, esta resposta agora recomenda o módulo io .

Use o método aberto do módulo de codecs.

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

Depois de chamar a função read () de f, um objeto Unicode codificado é retornado.

>>>f.read()
u'Capit\xe1l\n\n'

Se você conhece a codificação de um arquivo, o uso do pacote de codecs será muito menos confuso.

Consulte http://docs.python.org/library/codecs.html#codecs.open

Tim Swast
fonte
74
Funciona perfeitamente para escrever arquivos também, em vez de open(file,'w')não codecs.open(file,'w','utf-8')resolvido
Matt Connolly
1
Esta é resposta que eu estava procurando :)
Justin
6
O codecs.open(...)método também está totalmente de acordo com o with open(...):estilo, onde se withpreocupa em fechar o arquivo depois de tudo feito? Parece funcionar de qualquer maneira.
try-catch-finally
2
@ try-catch-finalmente Sim. Eu uso with codecs.open(...) as f:o tempo todo.
precisa saber é o seguinte
6
Eu gostaria de poder votar isso centenas de vezes. Depois de agonizar por vários dias por causa de problemas de codificação causados ​​por muitos dados mistos e de ler vivamente sobre a codificação, essa resposta é como a água no deserto. Gostaria de ter visto isso antes.
21813 Mike Girard
45

Agora tudo que você precisa no Python3 é open(Filename, 'r', encoding='utf-8')

[Editar em 10/02/2016 para esclarecimentos solicitados]

Python3 adicionou o parâmetro de codificação à sua função aberta. As seguintes informações sobre a função aberta são coletadas aqui: https://docs.python.org/3/library/functions.html#open

open(file, mode='r', buffering=-1, 
      encoding=None, errors=None, newline=None, 
      closefd=True, opener=None)

Codificação é o nome da codificação usada para decodificar ou codificar o arquivo. Isso deve ser usado apenas no modo de texto. A codificação padrão depende da plataforma (qualquer locale.getpreferredencoding () retorna), mas qualquer codificação de texto suportada pelo Python pode ser usada. Veja o módulo codecs para a lista de codificações suportadas.

Portanto, adicionando encoding='utf-8'como parâmetro à função open, a leitura e gravação do arquivo é feita como utf8 (que agora também é a codificação padrão de tudo o que é feito em Python).

Dakusan
fonte
Você poderia elaborar mais sua resposta adicionando um pouco mais de descrição sobre a solução que você fornece?
Abarisone
2
Parece este está disponível em python 2 usando os codecs Módulo - codecs.open('somefile', encoding='utf-8') stackoverflow.com/a/147756/149428
Taylor Edmiston
18

Então, eu encontrei uma solução para o que estou procurando, que é:

print open('f2').read().decode('string-escape').decode("utf-8")

Existem alguns codecs incomuns que são úteis aqui. Essa leitura específica permite obter representações UTF-8 no Python, copiá-las em um arquivo ASCII e fazer com que sejam lidas no Unicode. Sob o decodificador "string-escape", as barras não serão dobradas.

Isso permite o tipo de ida e volta que eu estava imaginando.

Gregg Lind
fonte
1
Boa resposta, eu testei as duas soluções (codecs.open(file,"r","utf-8")e simplesmente open(file,"r").read().decode("utf-8")e ambas funcionaram perfeitamente.
Eagle
Estou recebendo um "TypeError: str esperado, bytes ou objeto os.PathLike, não _io.TextIOWrapper", alguma idéia do porquê?
JinSnow
Eu acho que, considerando o número de upvotes, seria uma ótima idéia para aceitar a segunda resposta :)
Jacquot
14
# -*- 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
14

Na verdade, isso funcionou para mim para ler um arquivo com codificação UTF-8 no Python 3.2:

import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
    print(line)
Sina
fonte
6

Para ler uma string Unicode e depois enviar para HTML, fiz o seguinte:

fileline.decode("utf-8").encode('ascii', 'xmlcharrefreplace')

Útil para servidores http com tecnologia python.

praj
fonte
6

Você se deparou com o problema geral das codificações: como posso saber em que codificação está um arquivo?

Resposta: Você não pode , a menos que o formato do arquivo preveja isso. XML, por exemplo, começa com:

<?xml encoding="utf-8"?>

Este cabeçalho foi cuidadosamente escolhido para que possa ser lido, independentemente da codificação. No seu caso, não existe essa dica, portanto, nem o seu editor nem o Python têm idéia do que está acontecendo. Portanto, você deve usar o codecsmódulo e usarcodecs.open(path,mode,encoding) que fornece o bit ausente no Python.

Quanto ao seu editor, você deve verificar se ele oferece alguma maneira de definir a codificação de um arquivo.

O objetivo do UTF-8 é ser capaz de codificar caracteres de 21 bits (Unicode) como um fluxo de dados de 8 bits (porque essa é a única coisa que todos os computadores do mundo podem suportar). Mas como a maioria dos sistemas operacionais é anterior à era Unicode, eles não têm ferramentas adequadas para anexar as informações de codificação aos arquivos no disco rígido.

A próxima edição é a representação em Python. Isso é explicado perfeitamente no comentário de heikogerlach . Você deve entender que seu console pode exibir apenas ASCII. Para exibir Unicode ou qualquer coisa> = charcode 128, ele deve usar alguns meios de escapar. No seu editor, você não deve digitar a string de exibição com escape, mas o que a string significa (nesse caso, você deve inserir o trema e salvar o arquivo).

Dito isso, você pode usar a função Python eval () para transformar uma string de escape em uma string:

>>> x = eval("'Capit\\xc3\\xa1n\\n'")
>>> x
'Capit\xc3\xa1n\n'
>>> x[5]
'\xc3'
>>> len(x[5])
1

Como você pode ver, a string "\ xc3" foi transformada em um único caractere. Agora é uma sequência de 8 bits, codificada em UTF-8. Para obter Unicode:

>>> x.decode('utf-8')
u'Capit\xe1n\n'

Gregg Lind perguntou: Acho que faltam algumas peças aqui: o arquivo f2 contém: hex:

0000000: 4361 7069 745c 7863 335c 7861 316e  Capit\xc3\xa1n

codecs.open('f2','rb', 'utf-8'), por exemplo, lê todos eles em caracteres separados (esperado) Existe alguma maneira de gravar em um arquivo em ASCII que funcione?

Resposta: Isso depende do que você quer dizer. ASCII não pode representar caracteres> 127. Portanto, você precisa de alguma maneira de dizer "os próximos caracteres significam algo especial", que é o que a sequência "\ x" faz. Diz: Os próximos dois caracteres são o código de um único caractere. "\ u" faz o mesmo usando quatro caracteres para codificar Unicode até 0xFFFF (65535).

Portanto, você não pode gravar diretamente Unicode em ASCII (porque o ASCII simplesmente não contém os mesmos caracteres). Você pode escrever como escape de string (como em f2); nesse caso, o arquivo pode ser representado como ASCII. Ou você pode escrevê-lo como UTF-8; nesse caso, você precisa de um fluxo seguro de 8 bits.

Sua solução decode('string-escape')está funcionando, mas você deve estar ciente da quantidade de memória que usa: Três vezes a quantidade de uso codecs.open().

Lembre-se de que um arquivo é apenas uma sequência de bytes com 8 bits. Nem os bits nem os bytes têm um significado. É você quem diz "65 significa 'A'". Desde a\xc3\xa1 deve se tornar "à", mas o computador não tem como saber, especifique a codificação usada ao gravar o arquivo.

Aaron Digulla
fonte
Acho que faltam algumas peças aqui: o arquivo f2 contém: hex: 0000000: 4361 7069 745c 7863 335c 7861 316e 0a Capit \ xc3 \ xa1n. codecs.open ('f2', 'rb', 'utf-8'), por exemplo, lê todos eles em caracteres separados (esperado) Existe alguma maneira de gravar em um arquivo em ascii que funcione?
Gregg Lind
6

exceto codecs.open(), pode-se usar io.open()para trabalhar com Python2 ou Python3 para ler / gravar arquivo unicode

exemplo

import io

text = u'á'
encoding = 'utf8'

with io.open('data.txt', 'w', encoding=encoding, newline='\n') as fout:
    fout.write(text)

with io.open('data.txt', 'r', encoding=encoding, newline='\n') as fin:
    text2 = fin.read()

assert text == text2
Ryan
fonte
1
+1 io é muito melhor que codecs.
personal_cloud
Sim, usar io é melhor; Mas eu escrevi o com declaração como esta with io.open('data.txt', 'w', 'utf-8') as file:e tenho um erro: TypeError: an integer is required. Depois que mudei para with io.open('data.txt', 'w', encoding='utf-8') as file:e funcionou.
Evan Hu
5

Bem, seu editor de texto favorito não percebe que \xc3\xa1deveriam ser literais de caracteres, mas os interpreta como texto. É por isso que você obtém as barras invertidas duplas na última linha - agora é uma barra invertida real xc3etc. no seu arquivo.

Se você quiser ler e gravar arquivos codificados em Python, use melhor o módulo codecs .

Colar texto entre o terminal e os aplicativos é difícil, porque você não sabe qual programa interpretará seu texto usando qual codificação. Você pode tentar o seguinte:

>>> s = file("f1").read()
>>> print unicode(s, "Latin-1")
Capitán

Em seguida, cole essa sequência no seu editor e verifique se a armazena usando o Latin-1. Sob a suposição de que a área de transferência não deturpa a corda, a viagem de ida e volta deve funcionar.

Torsten Marek
fonte
4

A sequência \ x .. é algo específico do Python. Não é uma sequência universal de escape de bytes.

O modo como você realmente entra no ASCII não codificado em UTF-8 depende do seu sistema operacional e / ou do seu editor. Veja como você faz isso no Windows . Para OS X para entrar um com um acento agudo pode apenas bater option+ E, então A, e quase todos os editores de texto em OS X suporte UTF-8.

ʞɔıu
fonte
3

Você também pode melhorar a open()função original para trabalhar com arquivos Unicode, substituindo-a no lugar, usando a partialfunção A beleza desta solução é que você não precisa alterar nenhum código antigo. É transparente.

import codecs
import functools
open = functools.partial(codecs.open, encoding='utf-8')
hipertracker
fonte
1

Eu estava tentando analisar o iCal usando o Python 2.7.9:

icalendar import Calendário

Mas eu estava recebendo:

 Traceback (most recent call last):
 File "ical.py", line 92, in parse
    print "{}".format(e[attr])
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1' in position 7: ordinal not in range(128)

e foi corrigido com apenas:

print "{}".format(e[attr].encode("utf-8"))

(Agora, ele pode imprimir imagens favoritas.)

Alexx Roche
fonte
0

Encontrei a abordagem mais simples alterando a codificação padrão de todo o script para 'UTF-8':

import sys
reload(sys)
sys.setdefaultencoding('utf8')

qualquer declaração open, printou outra, será usada apenas utf8.

Funciona pelo menos para Python 2.7.9.

O Thx vai para https://markhneedham.com/blog/2015/05/21/python-unicodeencodeerror-ascii-codec-cant-encode-character-uxfc-in-position-11-ordinal-not-in-range128/ ( olhe o fim).

dr0i
fonte