Como fazer o interpretador Python lidar corretamente com caracteres não ASCII em operações de string?

104

Eu tenho uma string assim:

6 918 417 712

A maneira clara de cortar essa string (pelo que eu entendo Python) é simplesmente dizer que a string está em uma variável chamada s, obtemos:

s.replace('Â ', '')

Isso deve funcionar. Mas é claro que ele reclama que o caractere não ASCII '\xc2'no arquivo blabla.py não está codificado.

Nunca consegui entender como alternar entre as diferentes codificações.

Aqui está o código, ele realmente é igual ao anterior, mas agora está no contexto. O arquivo é salvo como UTF-8 no bloco de notas e possui o seguinte cabeçalho:

#!/usr/bin/python2.4
# -*- coding: utf-8 -*-

O código:

f = urllib.urlopen(url)

soup = BeautifulSoup(f)

s = soup.find('div', {'id':'main_count'})

#making a print 's' here goes well. it shows 6Â 918Â 417Â 712

s.replace('Â ','')

save_main_count(s)

Não vai além de s.replace...

Adergaard
fonte
1
Tentei todas as 4 respostas até agora. Não vá. Ainda obtendo UnicodeDecodeError: o codec 'ascii' não consegue decodificar o byte 0xc2 na posição 1: ordinal fora do intervalo (128)
adergaard
sua string Unicode deve ser prefixada comu
SilentGhost
@SilentGhost: como você pode ver, não há como ter certeza de que é uma string Unicode. Recebo uma string com o conteúdo mostrado acima, mas contém strings não ASCII. Esse é o verdadeiro problema. Suponho que seja unicode, pois não está no primeiro 128.
adergaard
O erro não tem nada a ver com a string de entrada. É uma string em seu código que gera esse erro!
SilentGhost
2
Aposto que é por isso que o Python 3 é tão rígido quanto à diferença entre strings e sequências de bytes, apenas para evitar esse tipo de confusão.
Mark Ransom

Respostas:

84

Python 2 usa asciicomo a codificação padrão para arquivos de origem, o que significa que você deve especificar outra codificação no topo do arquivo para usar caracteres Unicode não ASCII em literais. Python 3 usa utf-8como codificação padrão para arquivos de origem, portanto, isso não é um problema.

Consulte: http://docs.python.org/tutorial/interpreter.html#source-code-encoding

Para habilitar a codificação de origem utf-8, isso ficaria em uma das duas linhas principais:

# -*- coding: utf-8 -*-

O texto acima está nos documentos, mas também funciona:

# coding: utf-8

Considerações adicionais:

  • O arquivo de origem também deve ser salvo usando a codificação correta em seu editor de texto.

  • No Python 2, o literal unicode deve ter um uantes dele, como em s.replace(u"Â ", u"")Mas no Python 3, apenas use aspas. No Python 2, você pode from __future__ import unicode_literalsobter o comportamento do Python 3, mas esteja ciente de que isso afeta todo o módulo atual.

  • s.replace(u"Â ", u"")também falhará se snão for uma string Unicode.

  • string.replace retorna uma nova string e não edita no local, então certifique-se de usar o valor de retorno também

Jason S
fonte
4
Na verdade, você só precisa # coding: utf-8. -*-não é para decoração, mas é improvável que você precise disso. Acho que estava lá para cascas antigas.
fmalina
157
def removeNonAscii(s): return "".join(filter(lambda x: ord(x)<128, s))

editar: meu primeiro impulso é sempre usar um filtro, mas a expressão do gerador é mais eficiente em termos de memória (e mais curta) ...

def removeNonAscii(s): return "".join(i for i in s if ord(i)<128)

Lembre-se de que isso funcionará com a codificação UTF-8 (porque todos os bytes em caracteres multibyte têm o bit mais alto definido como 1).

Fortran
fonte
1
Eu recebo: TypeError: ord () esperava um caractere, mas foi encontrada uma string de comprimento 2
Ivelin
@Ivelin isso ocorre porque o "caractere" não está sendo interpretado como unicode adequado ... verifique se sua string de origem está prefixada com use for um literal.
fortran
35
>>> unicode_string = u"hello aåbäcö"
>>> unicode_string.encode("ascii", "ignore")
'hello abc'
truppo
fonte
4
Eu vejo os votos que você consegue, mas quando tento, diz: Não. UnicodeDecodeError: o codec 'ascii' não pode decodificar o byte 0xc2 na posição 1: ordinal fora do intervalo (128). Será que minha string original não está em Unicode? Bem, em qualquer caso. ele precisa
adergaard
2
Legal, obrigado. Posso sugerir o uso de .decode () no resultado para obtê-lo na codificação original?
AkiRoss
Se você estiver obtendo UnicodeDecodeError: 'ascii', tente converter a string no formato '' UTF-8 'antes de aplicar a função de codificação.
Sateesh
16

O código a seguir substituirá todos os caracteres não ASCII por pontos de interrogação.

"".join([x if ord(x) < 128 else '?' for x in s])
Visão
fonte
Por curiosidade, queria saber se existe algum motivo específico para substituí-lo pelo ponto de interrogação?
Mohsin de
6

Usando Regex:

import re

strip_unicode = re.compile("([^-_a-zA-Z0-9!@#%&=,/'\";:~`\$\^\*\(\)\+\[\]\.\{\}\|\?\<\>\\]+|[^\s]+)")
print strip_unicode.sub('', u'6Â 918Â 417Â 712')
Akoi Meexx
fonte
5

Tarde demais para uma resposta, mas a string original estava em UTF-8 e '\ xc2 \ xa0' é UTF-8 para NO-BREAK SPACE. Simplesmente decodifique a string original como s.decode('utf-8')(\ xa0 é exibido como um espaço quando decodificado incorretamente como Windows-1252 ou latin-1:

Exemplo (Python 3)

s = b'6\xc2\xa0918\xc2\xa0417\xc2\xa0712'
print(s.decode('latin-1')) # incorrectly decoded
u = s.decode('utf8') # correctly decoded
print(u)
print(u.replace('\N{NO-BREAK SPACE}','_'))
print(u.replace('\xa0','-')) # \xa0 is Unicode for NO-BREAK SPACE

Resultado

6 918 417 712
6 918 417 712
6_918_417_712
6-918-417-712
Mark Tolonen
fonte
3
#!/usr/bin/env python
# -*- coding: utf-8 -*-

s = u"6Â 918Â 417Â 712"
s = s.replace(u"Â", "") 
print s

Isso vai imprimir 6 918 417 712

Isaías
fonte
Não. UnicodeDecodeError: o codec 'ascii' não pode decodificar o byte 0xc2 na posição 1: ordinal fora do intervalo (128). Será que minha string original não está em Unicode? Bem, em qualquer caso. Provavelmente estou fazendo algo errado.
adergaard
@adergaard, você adicionou # - - coding: utf-8 - - no topo do arquivo de origem?
Nadia Alramli
Sim, veja o topo desta página novamente, eu editei o questoin e coloquei o código e os comentários do cabeçalho. Obrigado pela sua assistência.
adergaard
Acho que você terá que descobrir como obter as strings do documento html ou xml em Unicode. Mais informações sobre isso aqui: diveintopython.org/xml_processing/unicode.html
Isaías
2

Eu sei que é um tópico antigo, mas me senti obrigado a mencionar o método translate, que é sempre uma boa maneira de substituir todos os códigos de caracteres acima de 128 (ou outro, se necessário).

Uso : str. traduzir ( tabela [, deletechars] )

>>> trans_table = ''.join( [chr(i) for i in range(128)] + [' '] * 128 )

>>> 'Résultat'.translate(trans_table)
'R sultat'
>>> '6Â 918Â 417Â 712'.translate(trans_table)
'6  918  417  712'

A partir do Python 2.6 , você também pode definir a tabela como Nenhum e usar deletechars para excluir os caracteres que você não deseja, como nos exemplos mostrados nos documentos padrão em http://docs.python.org/library/stdtypes. html .

Com strings Unicode, a tabela de tradução não é uma string de 256 caracteres, mas um dict com ord () de caracteres relevantes como chaves. Mas, de qualquer forma, obter uma string ascii adequada de uma string Unicode é bastante simples, usando o método mencionado por truppo acima, a saber: unicode_string.encode ("ascii", "ignore")

Como resumo, se por algum motivo você absolutamente precisar obter uma string ascii (por exemplo, quando você levanta uma exceção padrão com raise Exception, ascii_message), você pode usar a seguinte função:

trans_table = ''.join( [chr(i) for i in range(128)] + ['?'] * 128 )
def ascii(s):
    if isinstance(s, unicode):
        return s.encode('ascii', 'replace')
    else:
        return s.translate(trans_table)

A coisa boa com traduzir é que você pode realmente converter caracteres acentuados em caracteres ascii não acentuados relevantes em vez de simplesmente excluí-los ou substituí-los por '?' Isso geralmente é útil, por exemplo, para fins de indexação.

Louis LC
fonte
Eu obtenho: TypeError: o mapeamento de caracteres deve retornar inteiro, Nenhum ou Unicode
Ivelin
1
s.replace(u'Â ', '')              # u before string is important

e tornar seu .pyarquivo Unicode.

SilentGhost
fonte
1

Este é um hack sujo, mas pode funcionar.

s2 = ""
for i in s:
    if ord(i) < 128:
        s2 += i
Corey D
fonte
0

Pelo que valeu a pena, meu conjunto de caracteres era utf-8e eu incluí a clássica # -*- coding: utf-8 -*-linha " ".

No entanto, descobri que não tinha Newlines Universal ao ler esses dados de uma página da web.

Meu texto tinha duas palavras, separadas por " \r\n". Eu estava apenas dividindo no \ne substituindo o "\n".

Assim que fiz um loop e vi o conjunto de caracteres em questão, percebi o erro.

Portanto, ele também pode estar dentro do conjunto de caracteres ASCII , mas um caractere que você não esperava.

Glen
fonte