¿Cómo hacer que el intérprete de Python maneje correctamente los caracteres no ASCII en las operaciones de cadena?

104

Tengo una cadena que se parece a eso:

6 918 417 712

La forma clara de recortar esta cadena (como entiendo Python) es simplemente decir que la cadena está en una variable llamada s, obtenemos:

s.replace('Â ', '')

Eso debería hacer el truco. Pero, por supuesto, se queja de que el carácter no ASCII del '\xc2'archivo blabla.py no está codificado.

Nunca pude entender cómo cambiar entre diferentes codificaciones.

Aquí está el código, realmente es el mismo que el anterior, pero ahora está en contexto. El archivo se guarda como UTF-8 en el bloc de notas y tiene el siguiente encabezado:

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

El 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)

No llega más allá de s.replace...

Adergaard
fuente
1
Probé todas las 4 respuestas hasta ahora. No vayas. Aún obteniendo el UnicodeDecodeError: el códec 'ascii' no puede decodificar el byte 0xc2 en la posición 1: ordinal no está en el rango (128)
adergaard
su cadena Unicode debe estar precedida poru
SilentGhost
@SilentGhost: como puede ver, no hay forma de estar seguro de que sea una cadena Unicode. Obtengo una cadena que tiene el contenido que se muestra arriba, pero contiene cadenas no ascii. Ese es el verdadero problema. Supongo que es unicode ya que no está en los primeros 128.
adergaard
El error no tiene nada que ver con la cadena entrante. ¡Es una cadena en su código la que genera este error!
SilentGhost
2
Apuesto a que es por eso que Python 3 es tan estricto con la diferencia entre cadenas y secuencias de bytes, solo para evitar este tipo de confusión.
Mark Ransom

Respuestas:

84

Python 2 utiliza asciicomo codificación predeterminada para los archivos de origen, lo que significa que debe especificar otra codificación en la parte superior del archivo para usar caracteres Unicode que no sean ASCII en literales. Python 3 se utiliza utf-8como codificación predeterminada para los archivos de origen, por lo que esto es un problema menor.

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

Para habilitar la codificación de origen utf-8, esto iría en una de las dos líneas superiores:

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

Lo anterior está en los documentos, pero esto también funciona:

# coding: utf-8

Consideraciones adicionales:

  • El archivo de origen también debe guardarse utilizando la codificación correcta en su editor de texto.

  • En Python 2, el literal Unicode debe tener un uantes, como en s.replace(u"Â ", u"")Pero en Python 3, solo use comillas. En Python 2, puede from __future__ import unicode_literalsobtener el comportamiento de Python 3, pero tenga en cuenta que esto afecta a todo el módulo actual.

  • s.replace(u"Â ", u"")también fallará si sno es una cadena Unicode.

  • string.replace devuelve una nueva cadena y no se edita en su lugar, así que asegúrese de usar el valor de retorno también

Jason S
fuente
4
En realidad, solo necesitas # coding: utf-8. -*-no es para decoración, pero es poco probable que lo necesite. Creo que estaba ahí para viejas conchas.
fmalina
157
def removeNonAscii(s): return "".join(filter(lambda x: ord(x)<128, s))

editar: mi primer impulso es siempre usar un filtro, pero la expresión del generador es más eficiente en memoria (y más corta) ...

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

Tenga en cuenta que se garantiza que esto funciona con la codificación UTF-8 (porque todos los bytes en caracteres multibyte tienen el bit más alto establecido en 1).

fortran
fuente
1
Recibo: TypeError: ord () esperaba un carácter, pero se encontró una cadena de longitud 2
Ivelin
@Ivelin eso se debe a que el "carácter" no se está interpretando como unicode adecuado ... verifique que su cadena de origen tenga el prefijo usi es un literal.
fortran
35
>>> unicode_string = u"hello aåbäcö"
>>> unicode_string.encode("ascii", "ignore")
'hello abc'
truppo
fuente
4
Veo los votos que obtienes, pero cuando lo intento, dice: No. UnicodeDecodeError: el códec 'ascii' no puede decodificar el byte 0xc2 en la posición 1: ordinal no está en el rango (128). ¿Podría ser que mi cadena original no esté en Unicode? Bueno, en cualquier caso. necesita
adergaard
2
Genial gracias. ¿Puedo sugerir usar .decode () en el resultado para obtenerlo en la codificación original?
AkiRoss
Si obtiene UnicodeDecodeError: 'ascii', intente convertir la cadena al formato '' UTF-8 'antes de aplicar la función de codificación.
Sateesh
16

El siguiente código reemplazará todos los caracteres no ASCII con signos de interrogación.

"".join([x if ord(x) < 128 else '?' for x in s])
Visión
fuente
Por curiosidad, quería saber eso, ¿hay alguna razón específica para reemplazarlo con el signo de interrogación?
Mohsin
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
fuente
5

Demasiado tarde para una respuesta, pero la cadena original estaba en UTF-8 y '\ xc2 \ xa0' es UTF-8 para NO-BREAK SPACE. Simplemente decodifique la cadena original como s.decode('utf-8')(\ xa0 se muestra como un espacio cuando se decodifica incorrectamente como Windows-1252 o latin-1:

Ejemplo (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

Salida

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

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

Esto se imprimirá 6 918 417 712

Isaías
fuente
No UnicodeDecodeError: el códec 'ascii' no puede decodificar el byte 0xc2 en la posición 1: ordinal no está en el rango (128). ¿Podría ser que mi cadena original no esté en Unicode? Bueno, en cualquier caso. Probablemente estoy haciendo algo mal.
adergaard
@adergaard, ¿agregó # - - coding: utf-8 - - en la parte superior del archivo fuente?
Nadia Alramli
Sí, vea la parte superior de esta página nuevamente, he editado la pregunta e ingresado el código y los comentarios del encabezado. Gracias por tu ayuda.
adergaard
Creo que tendrá que averiguar cómo obtener las cadenas del documento html o xml en unicode. Más información sobre eso aquí: diveintopython.org/xml_processing/unicode.html
Isaías
2

Sé que es un hilo antiguo, pero me sentí obligado a mencionar el método de traducción, que siempre es una buena manera de reemplazar todos los códigos de caracteres por encima de 128 (u otro si es necesario).

Uso : str. traducir ( tabla [, eliminar caracteres] )

>>> 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 de Python 2.6 , también puede establecer la tabla en Ninguno y usar deletechars para eliminar los caracteres que no desea, como en los ejemplos que se muestran en los documentos estándar en http://docs.python.org/library/stdtypes. html .

Con cadenas Unicode, la tabla de traducción no es una cadena de 256 caracteres sino un dict con el ord () de los caracteres relevantes como claves. Pero de todos modos, obtener una cadena ascii adecuada de una cadena unicode es bastante simple, usando el método mencionado por truppo anteriormente, a saber: unicode_string.encode ("ascii", "ignore")

Como resumen, si por alguna razón es absolutamente necesario obtener una cadena ascii (por ejemplo, cuando genera una excepción estándar con raise Exception, ascii_message), puede usar la siguiente función:

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)

Lo bueno de translate es que puedes convertir caracteres acentuados en caracteres ascii relevantes sin acentos en lugar de simplemente eliminarlos o reemplazarlos por '?'. Esto suele ser útil, por ejemplo, para fines de indexación.

Luis LC
fuente
Recibo: TypeError: el mapeo de caracteres debe devolver un entero, Ninguno o Unicode
Ivelin
1
s.replace(u'Â ', '')              # u before string is important

y haga que su .pyarchivo sea unicode.

SilentGhost
fuente
1

Este es un truco sucio, pero puede funcionar.

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

Por lo que valía, mi juego de caracteres era utf-8y había incluido la # -*- coding: utf-8 -*-línea clásica " ".

Sin embargo, descubrí que no tenía Universal Newlines al leer estos datos de una página web.

Mi texto tenía dos palabras, separadas por " \r\n". Solo estaba dividiendo \ny reemplazando el "\n".

Una vez que recorrí y vi el juego de caracteres en cuestión, me di cuenta del error.

Entonces, también podría estar dentro del conjunto de caracteres ASCII , pero un carácter que no esperabas.

Cañada
fuente