282 votes

Remplacer les caractères non ASCII par un seul espace.

Je dois remplacer tous les caractères non ASCII ( \x00 - \x7F ) avec un espace. Je suis surpris que cela ne soit pas très facile en Python, à moins que je ne manque quelque chose. La fonction suivante supprime simplement tous les caractères non ASCII :

def remove_non_ascii_1(text):

    return ''.join(i for i in text if ord(i)<128)

Et celui-ci remplace les caractères non-ASCII par le nombre d'espaces correspondant à la quantité d'octets dans le point de code du caractère (c'est-à-dire le est remplacé par 3 espaces) :

def remove_non_ascii_2(text):

    return re.sub(r'[^\x00-\x7F]',' ', text)

Comment puis-je remplacer tous les caractères non ASCII par un seul espace ?

Sur el myriade de de similaire SO questions , aucun adresse caractère remplacement comme s'est opposé à à décapage , y s'adresse en outre à tous les caractères non ascii et non à un caractère spécifique.

57 votes

Wow, vous avez vraiment fait de gros efforts pour montrer autant de liens. +1 dès que le jour se renouvelle !

3 votes

Vous semblez avoir manqué celui-ci stackoverflow.com/questions/1342000/

0 votes

J'aimerais voir un exemple d'entrée qui présente des problèmes.

272voto

Martijn Pieters Points 271458

Votre ''.join() L'expression est filtrage en supprimant tout ce qui n'est pas ASCII ; vous pouvez utiliser une expression conditionnelle à la place :

return ''.join([i if ord(i) < 128 else ' ' for i in text])

Cette méthode traite les caractères un par un et utilise toujours un espace par caractère remplacé.

Votre expression régulière doit juste remplacer consécutif les caractères non ASCII avec un espace :

re.sub(r'[^\x00-\x7F]+',' ', text)

Notez le + là.

19 votes

@dstromberg : plus lent ; str.join() besoins une liste (il passera deux fois sur les valeurs), et une expression de générateur sera d'abord convertie en une. Lui donner une compréhension de liste est simplement plus rapide. Voir ce poste .

1 votes

Le premier morceau de code insère plusieurs blancs par caractère si vous lui fournissez une chaîne d'octets UTF-8.

0 votes

@MarkRansom : Je pensais qu'il s'agissait de Python 3.

65voto

Alvaro Fuentes Points 860

Pour que vous obteniez la représentation la plus proche de votre chaîne originale, je vous recommande le module unidecode :

from unidecode import unidecode
def remove_non_ascii(text):
    return unidecode(unicode(text, encoding = "utf-8"))

Vous pouvez ensuite l'utiliser dans une chaîne de caractères :

remove_non_ascii("Ceñía")
Cenia

24voto

Mark Tolonen Points 32702

Pour caractère utilisez des chaînes de caractères Unicode :

PythonWin 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32.
>>> s='ABCdef'
>>> import re
>>> re.sub(r'[^\x00-\x7f]',r' ',s)   # Each char is a Unicode codepoint.
'ABC  def'
>>> b = s.encode('utf8')
>>> re.sub(rb'[^\x00-\x7f]',rb' ',b) # Each char is a 3-byte UTF-8 sequence.
b'ABC      def'

Mais notez que vous aurez toujours un problème si votre chaîne contient des caractères Unicode décomposés (caractère séparé et combinaison de marques d'accent, par exemple) :

>>> s = 'mañana'
>>> len(s)
6
>>> import unicodedata as ud
>>> n=ud.normalize('NFD',s)
>>> n
'manana'
>>> len(n)
7
>>> re.sub(r'[^\x00-\x7f]',r' ',s) # single codepoint
'ma ana'
>>> re.sub(r'[^\x00-\x7f]',r' ',n) # only combining mark replaced
'man ana'

0 votes

Merci, c'est une observation importante. Si vous trouvez un moyen logique de traiter le cas des marques combinées, je serai heureux d'ajouter une prime à la question. Je suppose que le mieux serait de supprimer le signe de combinaison tout en laissant le caractère non combiné intact.

1 votes

Une solution partielle consiste à utiliser ud.normalize('NFC',s) pour combiner des marques, mais toutes les combinaisons ne sont pas représentées par des points de code uniques. Vous auriez besoin d'une solution plus intelligente en regardant les ud.category() du personnage.

1 votes

@dotancohen : il existe une notion de "caractère perçu par l'utilisateur" dans Unicode qui peut couvrir plusieurs codepoints Unicode. \X (grappe de graphèmes étendue) regex (supporté par regex ) permet d'itérer sur de tels caractères (note : "Les graphèmes ne sont pas nécessairement des séquences de caractères combinés, et les séquences de caractères combinés ne sont pas nécessairement des graphèmes". ).

12voto

irdb Points 11

Si le caractère de remplacement peut être '?' au lieu d'un espace, je suggérerais alors result = text.encode('ascii', 'replace').decode() :

"""Test the performance of different non-ASCII replacement methods."""

import re
from timeit import timeit

# 10_000 is typical in the project that I'm working on and most of the text
# is going to be non-ASCII.
text = 'Æ' * 10_000

print(timeit(
    """
result = ''.join([c if ord(c) < 128 else '?' for c in text])
    """,
    number=1000,
    globals=globals(),
))

print(timeit(
    """
result = text.encode('ascii', 'replace').decode()
    """,
    number=1000,
    globals=globals(),
))

Résultats :

0.7208260721400134
0.009975979187503592

8voto

parsecer Points 696

Et celui-là ?

def replace_trash(unicode_string):
     for i in range(0, len(unicode_string)):
         try:
             unicode_string[i].encode("ascii")
         except:
              #means it's non-ASCII
              unicode_string=unicode_string[i].replace(" ") #replacing it with a single space
     return unicode_string

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X