Comment puis-je supprimer des caractères non ASCII mais laisser des points et des espaces en utilisant Python?

100

Je travaille avec un fichier .txt. Je veux une chaîne du texte du fichier sans caractères non ASCII. Cependant, je veux laisser des espaces et des périodes. Pour le moment, je les dépouille aussi. Voici le code:

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

Comment dois-je modifier onlyascii () pour laisser des espaces et des points? J'imagine que ce n'est pas trop compliqué mais je ne peux pas le comprendre.

Alexwlchan
la source
Merci (sincèrement) pour la clarification John. J'ai compris que les espaces et les points sont des caractères ASCII. Cependant, je supprimais les deux involontairement en essayant de supprimer uniquement les caractères non ASCII. Je vois comment ma question aurait pu impliquer le contraire.
@PoliticalEconomist: Votre problème est encore très sous-spécifié. Voyez ma réponse.
John Machin

Réponses:

187

Vous pouvez filtrer tous les caractères de la chaîne qui ne sont pas imprimables à l'aide de string.printable , comme ceci:

>>> 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 sur ma machine contient:

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

EDIT: Sur Python 3, le filtre retournera un itérable. La manière correcte de récupérer une chaîne serait:

''.join(filter(lambda x: x in printable, s))
jterrace
la source
2
que se passe-t-il avec ces caractères imprimables inférieurs à l'ordinal 48?
joaquin
38
Le seul problème avec l'utilisation filterest qu'il renvoie un itérable. Si vous avez besoin d' un retour de chaîne (comme je l'ai fait parce que je avais besoin quand faire la compression de la liste) , puis procédez comme suit: ''.join(filter(lambda x: x in string.printable, s).
cjbarth
5
@cjbarth - comment est spécifique à Python 3, mais très utile. Merci!
sous
7
Pourquoi ne pas utiliser l' expression régulière: re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string). Voir ce fil stackoverflow.com/a/20079244/658497
Noam Manos
1
@NoamManos c'était 4-5 fois plus rapide pour moi que dans la solution join ... filter ... lambda, merci.
artfulrobot
95

Un moyen simple de passer à un codec différent consiste à utiliser encode () ou decode (). Dans votre cas, vous souhaitez convertir en ASCII et ignorer tous les symboles qui ne sont pas pris en charge. Par exemple, la lettre suédoise å n'est pas un caractère 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

Éditer:

Python3: str -> octets -> 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 (décoder et encoder dans l'ordre inverse)

>>> "hej d\xe5".decode("ascii", errors="ignore").encode()
'hej d'
Zweedeend
la source
16
Je reçoisUnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 27
Xodarap777
2
J'ai eu cette erreur lorsque j'ai mis le caractère unicode réel dans la chaîne via un copier-coller. Lorsque vous spécifiez une chaîne comme u'thestring ', l'encodage fonctionne correctement.
Ben Liyanage
2
Fonctionne uniquement sur Py3, mais c'est élégant.
gaborous
7
Pour ceux qui obtiennent la même erreur que @ Xodarap777: vous devez d'abord .decode () la chaîne, et seulement après cela encoder. Par exemples.decode('utf-8').encode('ascii', errors='ignore')
Spc_555
30

Selon @artfulrobot, cela devrait être plus rapide que filter et lambda:

re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string) 

Voir plus d'exemples ici http://stackoverflow.com/questions/20078816/replace-non-ascii-characters-with-a-single-space/20079244#20079244

Noam Manos
la source
1
Cette solution répond à la question posée par OP, mais sachez qu'elle ne supprimera pas les caractères non imprimables inclus dans ASCII, ce que je pense être ce que OP avait l'intention de demander.
Danilo Souza Morães
6

Votre question est ambiguë; les deux premières phrases prises ensemble impliquent que vous pensez que l'espace et le "point" sont des caractères non ASCII. Ceci est une erreur. Tous les caractères tels que ord (char) <= 127 sont des caractères ASCII. Par exemple, votre fonction exclut ces caractères! "# $% & \ '() * +, -. / Mais en inclut plusieurs autres, par exemple [] {}.

Veuillez prendre du recul, réfléchir un peu et modifier votre question pour nous dire ce que vous essayez de faire, sans mentionner le mot ASCII, et pourquoi vous pensez que les caractères tels que ord (char)> = 128 sont ignorables. Aussi: quelle version de Python? Quel est le codage de vos données d'entrée?

Veuillez noter que votre code lit le fichier d'entrée entier comme une seule chaîne, et votre commentaire ("excellente solution") à une autre réponse implique que vous ne vous souciez pas des nouvelles lignes dans vos données. Si votre fichier contient deux lignes comme ceci:

this is line 1
this is line 2

le résultat serait 'this is line 1this is line 2'... est-ce ce que vous voulez vraiment?

Une meilleure solution comprendrait:

  1. un meilleur nom pour la fonction de filtre que onlyascii
  2. reconnaissance qu'une fonction de filtre doit simplement renvoyer une valeur de vérité si l'argument doit être conservé:

    def filter_func(char):
        return char == '\n' or 32 <= ord(char) <= 126
    # and later:
    filtered_data = filter(filter_func, data).lower()
John Machin
la source
Cette réponse est très utile à ceux d'entre nous qui viennent demander quelque chose de similaire au PO, et votre proposition de réponse est utilement pythonique. Je trouve cependant étrange qu'il n'y ait pas de solution plus efficace au problème tel que vous l'avez interprété (que je rencontre souvent) - caractère par caractère, cela prend beaucoup de temps dans un très gros fichier.
Xodarap777
5

Vous pouvez utiliser le code suivant pour supprimer les lettres non anglaises:

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

Cela reviendra

123456790 ABC #%? . ()

Noha Elprince
la source
1

Si vous voulez des caractères ascii imprimables, vous devriez probablement corriger votre code pour:

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

ceci équivaut à string.printable(réponse de @jterrace), sauf pour l'absence de retours et de tabulations ('\ t', '\ n', '\ x0b', '\ x0c' et '\ r') mais ne correspond pas à la gamme sur votre question

Joaquin
la source
1
Légèrement plus simple: lambda x: 32 <= ord (x) <= 126
jterrace
ce n'est pas la même chose que string.printable car il laisse de côté string.whitespace, bien que cela puisse être ce que l'OP veut, cela dépend de choses comme \ n et \ t.
jterrace
@jterrace right, inclut un espace (ord 32) mais pas de retours et de tabulations
joaquin
oui, juste commenter "c'est équivalent à string.printable", mais pas vrai
jterrace
J'ai édité la réponse, merci! la question OP est trompeuse si vous ne la lisez pas attentivement.
joaquin
1

Travailler mon chemin à travers Fluent Python (Ramalho) - hautement recommandé. List Compréhension One-ish-Liners inspirés du Chapitre 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
la source
Cela ne permettrait pas les symboles ASCII standard, tels que les puces, le symbole des degrés, le symbole du copyright, le symbole du yen, etc. De plus, votre premier exemple inclut des symboles non imprimables, tels que BELL, ce qui n'est pas souhaitable.
SherylHohman