¿Cómo puedo eliminar caracteres que no sean ASCII pero dejar puntos y espacios usando Python?

100

Estoy trabajando con un archivo .txt. Quiero una cadena del texto del archivo sin caracteres que no sean ASCII. Sin embargo, quiero dejar espacios y puntos. En la actualidad, también los estoy quitando. Aquí está el código:

def onlyascii(char):
    if ord(char) < 48 or ord(char) > 127: return ''
    else: return char

def get_my_string(file_path):
    f=open(file_path,'r')
    data=f.read()
    f.close()
    filtered_data=filter(onlyascii, data)
    filtered_data = filtered_data.lower()
    return filtered_data

¿Cómo debo modificar onlyascii () para dejar espacios y puntos? Imagino que no es demasiado complicado, pero no puedo entenderlo.

alexwlchan
fuente
Gracias (sinceramente) por la aclaración John. Entendí que los espacios y los puntos son caracteres ASCII. Sin embargo, los estaba eliminando sin querer mientras trataba de eliminar solo caracteres que no eran ASCII. Veo cómo mi pregunta podría haber implicado lo contrario.
@PoliticalEconomist: Su problema aún está muy poco especificado. Mira mi respuesta.
John Machin

Respuestas:

187

Puede filtrar todos los caracteres de la cadena que no se pueden imprimir usando string.printable , así:

>>> s = "some\x00string. with\x15 funny characters"
>>> import string
>>> printable = set(string.printable)
>>> filter(lambda x: x in printable, s)
'somestring. with funny characters'

string.printable en mi máquina contiene:

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c

EDITAR: En Python 3, el filtro devolverá un iterable. La forma correcta de obtener un respaldo de cadena sería:

''.join(filter(lambda x: x in printable, s))
terraza
fuente
2
¿Qué pasa con esos caracteres imprimibles que están por debajo del ordinal 48?
joaquin
38
El único problema con el uso filteres que devuelve un iterable. Si necesita una copia de la cadena (como lo hice porque necesitaba esto al hacer la compresión de la lista) y luego hacer esto: ''.join(filter(lambda x: x in string.printable, s).
cjbarth
5
@cjbarth: el comentario es específico de Python 3, pero muy útil. ¡Gracias!
subdesmontaje
7
¿Por qué no usar expresiones regulares re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string)? Ver este hilo stackoverflow.com/a/20079244/658497
Noam Manos
1
@NoamManos esto fue 4-5 veces más rápido para mí que en la solución join ... filter ... lambda, gracias.
artfulrobot
95

Una forma fácil de cambiar a un códec diferente es utilizando encode () o decode (). En su caso, desea convertir a ASCII e ignorar todos los símbolos que no son compatibles. Por ejemplo, la letra sueca å no es un carácter ASCII:

    >>>s = u'Good bye in Swedish is Hej d\xe5'
    >>>s = s.encode('ascii',errors='ignore')
    >>>print s
    Good bye in Swedish is Hej d

Editar:

Python3: str -> bytes -> str

>>>"Hej då".encode("ascii", errors="ignore").decode()
'hej d'

Python2: unicode -> str -> unicode

>>> u"hej då".encode("ascii", errors="ignore").decode()
u'hej d'

Python2: str -> unicode -> str (decodificar y codificar en orden inverso)

>>> "hej d\xe5".decode("ascii", errors="ignore").encode()
'hej d'
Zweedeend
fuente
16
ReciboUnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 27
Xodarap777
2
Recibí ese error cuando puse el carácter Unicode real en la cadena a través de copiar y pegar. Cuando especifica una cadena como codificación u'thestring 'funciona correctamente.
Ben Liyanage
2
Funciona solo en Py3, pero es elegante.
gaborous
7
Para aquellos que reciben el mismo error que @ Xodarap777: primero deben .decode () la cadena, y solo después de esa codificación. Por ejemplos.decode('utf-8').encode('ascii', errors='ignore')
Spc_555
6

Tu pregunta es ambigua; las dos primeras oraciones juntas implican que cree que el espacio y el "punto" son caracteres no ASCII. Esto es incorrecto. Todos los caracteres tales que ord (char) <= 127 son caracteres ASCII. Por ejemplo, ¡su función excluye estos caracteres! "# $% & \ '() * +, -. / Pero incluye varios otros, por ejemplo, [] {}.

Por favor, retroceda, piense un poco y edite su pregunta para decirnos qué está tratando de hacer, sin mencionar la palabra ASCII, y por qué cree que los caracteres tales como ord (char)> = 128 son ignorables. Además: ¿qué versión de Python? ¿Cuál es la codificación de sus datos de entrada?

Tenga en cuenta que su código lee todo el archivo de entrada como una sola cadena, y su comentario ("gran solución") a otra respuesta implica que no le importan las nuevas líneas en sus datos. Si su archivo contiene dos líneas como esta:

this is line 1
this is line 2

el resultado sería 'this is line 1this is line 2'... ¿es eso lo que realmente quieres?

Una mejor solución incluiría:

  1. un nombre mejor para la función de filtro que onlyascii
  2. Reconocimiento de que una función de filtro simplemente necesita devolver un valor veraz si se quiere retener el argumento:

    def filter_func(char):
        return char == '\n' or 32 <= ord(char) <= 126
    # and later:
    filtered_data = filter(filter_func, data).lower()
John Machin
fuente
Esta respuesta es muy útil para aquellos de nosotros que vamos a preguntar algo similar al OP, y su respuesta propuesta es útilmente pitónica. Sin embargo, me parece extraño que no haya una solución más eficiente al problema tal como lo interpretó (con lo que me encuentro a menudo): carácter por carácter, esto lleva mucho tiempo en un archivo muy grande.
Xodarap777
5

Puede utilizar el siguiente código para eliminar letras que no estén en inglés:

import re
str = "123456790 ABC#%? .(朱惠英)"
result = re.sub(r'[^\x00-\x7f]',r'', str)
print(result)

Esto volverá

123456790 ABC #%? . ()

Noha Elprince
fuente
1

Si desea caracteres ascii imprimibles, probablemente debería corregir su código para:

if ord(char) < 32 or ord(char) > 126: return ''

esto es equivalente a string.printable(respuesta de @jterrace), excepto por la ausencia de retornos y pestañas ('\ t', '\ n', '\ x0b', '\ x0c' y '\ r') pero no corresponde a el rango de tu pregunta

Joaquín
fuente
1
Un poco más simple: lambda x: 32 <= ord (x) <= 126
jterrace
eso no es lo mismo que string.printable porque omite string.whitespace, aunque eso podría ser lo que quiere el OP, depende de cosas como \ n y \ t.
jterrace
@jterrace a la derecha, incluye espacio (ord 32) pero sin devoluciones ni pestañas
joaquin
sí, solo comentando "esto es equivalente a string.printable", pero no es cierto
jterrace
Edité la respuesta, ¡gracias! la pregunta de OP es engañosa si no la lee con atención.
joaquin
1

Trabajando en Fluent Python (Ramalho) - muy recomendable. Enumere las frases sencillas de comprensión inspiradas en el Capítulo 2:

onlyascii = ''.join([s for s in data if ord(s) < 127])
onlymatch = ''.join([s for s in data if s in
              'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'])
Matthew Dunn
fuente
Esto no permitiría símbolos ASCII estándar, como viñetas, símbolo de grados, símbolo de derechos de autor, símbolo de Yen, etc. Además, su primer ejemplo incluye símbolos no imprimibles, como BELL, que no es deseable.
SherylHohman