587 votes

Quelle est la meilleure façon de supprimer les accents (normaliser) dans une chaîne unicode Python ?

J'ai une chaîne Unicode en Python, et je voudrais supprimer tous les accents (diacritiques).

J'ai trouvé sur le web une façon élégante de le faire (en Java) :

  1. convertir la chaîne Unicode en son forme longue normalisée (avec un caractère distinct pour les lettres et les diacritiques)
  2. supprimer tous les caractères dont le type Unicode est "diacritique".

Dois-je installer une bibliothèque telle que pyICU ou est-ce possible avec la seule bibliothèque standard de Python ? Et qu'en est-il de Python 3 ?

Remarque importante : je souhaite éviter les codes comportant une correspondance explicite entre les caractères accentués et leur équivalent non accentué.

573voto

Christian Oudard Points 13461

Unidecode est la réponse correcte à cette question. Il translittère n'importe quelle chaîne unicode dans la représentation la plus proche possible en texte ascii.

Exemple :

accented_string = u'Málaga'
# accented_string is of type 'unicode'
import unidecode
unaccented_string = unidecode.unidecode(accented_string)
# unaccented_string contains 'Malaga'and is of type 'str'

0 votes

Oui, c'est une meilleure solution que de simplement décaper les accents. Elle fournit des translittérations beaucoup plus utiles pour les langues qui ont des conventions pour écrire les mots en ASCII.

83 votes

Cela semble bien fonctionner avec le chinois, mais la transformation du nom français "François" donne malheureusement "FranASSois", ce qui n'est pas très bon, comparé au "François" plus naturel.

11 votes

Cela dépend de ce que vous essayez d'obtenir. Par exemple, je suis en train de faire une recherche et je ne veux pas translittérer le grec, le russe ou le chinois, je veux juste remplacer "a/e/s/c" par "a/e/s/c".

309voto

oefe Points 9122

Que dites-vous de ça ?

import unicodedata
def strip_accents(s):
   return ''.join(c for c in unicodedata.normalize('NFD', s)
                  if unicodedata.category(c) != 'Mn')

Cela fonctionne aussi pour les lettres grecques :

>>> strip_accents(u"A \u00c0 \u0394 \u038E")
u'A A \u0394 \u03a5'
>>> 

Le site catégorie de caractère "Mn" signifie Nonspacing_Mark qui est similaire à unicodedata.combining dans la réponse de MiniQuark (je n'ai pas pensé à unicodedata.combining, mais c'est probablement la meilleure solution, car elle est plus explicite).

Et n'oubliez pas que ces manipulations peuvent altérer considérablement le sens du texte. Les accents, les trémas, etc. ne sont pas de la "décoration".

6 votes

Ce ne sont pas des caractères composés, malheureusement - même si "" est nommé "LATIN SMALL LETTER L WITH STROKE" ! Vous devrez soit jouer avec l'analyse syntaxique unicodedata.name Vous pouvez aussi utiliser un tableau similaire - dont vous auriez besoin pour les lettres grecques de toute façon (c'est juste "GREEK CAPITAL LETTER ALPHA").

2 votes

@andi, j'ai bien peur de ne pas pouvoir deviner ce que vous voulez dire. L'échange de courriels reflète ce que j'ai écrit ci-dessus : Parce que la lettre "" n'est pas une lettre accentuée (et n'est pas traitée comme telle dans la norme Unicode), elle n'a pas de décomposition.

2 votes

@alexis (suivi tardif) : Cela fonctionne parfaitement bien pour le grec également - par exemple, "GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA" est normalisé en "GREEK CAPITAL LETTER ALPHA" comme prévu. À moins que vous ne fassiez référence à translittération (par exemple, "" "a"), ce qui n'est pas la même chose que "supprimer les accents"...

171voto

MiniQuark Points 8927

Je viens de trouver cette réponse sur le Web :

import unicodedata

def remove_accents(input_str):
    nfkd_form = unicodedata.normalize('NFKD', input_str)
    only_ascii = nfkd_form.encode('ASCII', 'ignore')
    return only_ascii

Cela fonctionne bien (pour le français, par exemple), mais je pense que la deuxième étape (suppression des accents) pourrait être mieux gérée que la suppression des caractères non-ASCII, car cela échouera pour certaines langues (le grec, par exemple). La meilleure solution serait probablement de supprimer explicitement les caractères unicode qui sont marqués comme étant des diacritiques.

Modifier Le résultat : ceci fait l'affaire :

import unicodedata

def remove_accents(input_str):
    nfkd_form = unicodedata.normalize('NFKD', input_str)
    return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])

unicodedata.combining(c) retournera vrai si le caractère c peut être combiné avec le caractère précédent, c'est-à-dire principalement s'il s'agit d'un diacritique.

Edit 2 : remove_accents s'attend à ce qu'un unicode et non une chaîne d'octets. Si vous avez une chaîne d'octets, vous devez la décoder en une chaîne unicode comme ceci :

encoding = "utf-8" # or iso-8859-15, or cp1252, or whatever encoding you use
byte_string = b"café"  # or simply "café" before python 3.
unicode_string = byte_string.decode(encoding)

6 votes

J'ai dû ajouter 'utf8' à unicode : nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))

0 votes

@Jabba : , 'utf8' est un "filet de sécurité" nécessaire si vous testez l'entrée dans le terminal (qui par défaut n'utilise pas l'unicode). Mais en général, vous ne ont pour l'ajouter, puisque si vous supprimez les accents alors input_str est très probablement déjà en utf8. Il n'y a pas de mal à être sûr, cependant.

0 votes

>>> def remove_accents(input_str) : ... nkfd_form = unicodedata.normalize('NFKD', unicode(input_str)) ... return u"".join([c for c in nkfd_form if not unicodedata.combining(c)]) ... >>> remove_accents('é') Traceback (dernier appel le plus récent) : File "<stdin>", line 1, in <module> File "<stdin>", line 2, in remove_accents UnicodeDecodeError : 'ascii' codec can't decode byte 0xc3 in position 0 : ordinal not in range(128)

29voto

lenz Points 448

Cela permet de traiter non seulement les accents, mais aussi les "traits" (comme dans ø, etc.) :

import unicodedata as ud

def rmdiacritics(char):
    '''
    Return the base character of char, by "removing" any
    diacritics like accents or curls and strokes and the like.
    '''
    desc = ud.name(char)
    cutoff = desc.find(' WITH ')
    if cutoff != -1:
        desc = desc[:cutoff]
        try:
            char = ud.lookup(desc)
        except KeyError:
            pass  # removing "WITH ..." produced an invalid name
    return char

C'est la méthode la plus élégante à laquelle je pense (et elle a été mentionnée par Alexis dans un commentaire sur cette page), bien que je ne pense pas qu'elle soit très élégante. En fait, c'est plus un hack, comme indiqué dans les commentaires, puisque les noms Unicode sont - vraiment juste des noms, ils ne donnent aucune garantie d'être cohérents ou quoi que ce soit.

Il existe encore des lettres spéciales qui ne sont pas gérées par ce système, comme les lettres tournées et inversées, car leur nom unicode ne contient pas "WITH". De toute façon, cela dépend de ce que vous voulez faire. J'ai parfois eu besoin de supprimer les accents pour obtenir l'ordre de tri des dictionnaires.

NOTE D'ÉDITION :

Incorporation des suggestions des commentaires (gestion des erreurs de recherche, code Python-3).

8 votes

Vous devez attraper l'exception si le nouveau symbole n'existe pas. Par exemple, il y a SQUARE WITH VERTICAL FILL , mais il n'y a pas de SQUARE. (sans oublier que ce code transforme UMBRELLA WITH RAIN DROPS en UMBRELLA ).

0 votes

Cela semble élégant en exploitant les descriptions sémantiques des caractères qui sont disponibles. Avons-nous vraiment besoin du unicode avec python 3 ? Je pense qu'un regex plus serré à la place de la fonction find éviterait tous les problèmes mentionnés dans le commentaire ci-dessus, et aussi, la mémorisation aiderait la performance quand c'est un chemin de code critique.

1 votes

@matanster : non, il s'agit d'une ancienne réponse datant de l'époque de Python-2 ; la fonction unicode typecast n'est plus approprié dans Python 3. Quoi qu'il en soit, d'après mon expérience, il n'existe pas de solution universelle et élégante à ce problème. Selon l'application, toute approche a ses avantages et ses inconvénients. Des outils axés sur la qualité comme unidecode sont basés sur des tables fabriquées à la main. Certaines ressources (tables, algorithmes) sont fournies par Unicode, par exemple pour la collation.

15voto

aseagram Points 123

En réponse à la réponse de @MiniQuark :

J'essayais de lire un fichier csv qui était à moitié français (contenant des accents) et aussi quelques chaînes de caractères qui deviendraient éventuellement des entiers et des flottants. En guise de test, j'ai créé un fichier test.txt qui ressemblait à ça :

Montréal, über, 12.89, Mère, Françoise, noël, 889

Je devais inclure des lignes 2 et 3 pour le faire fonctionner (que j'ai trouvé dans un ticket python), ainsi que d'intégrer le commentaire de @Jabba :

import sys 
reload(sys) 
sys.setdefaultencoding("utf-8")
import csv
import unicodedata

def remove_accents(input_str):
    nkfd_form = unicodedata.normalize('NFKD', unicode(input_str))
    return u"".join([c for c in nkfd_form if not unicodedata.combining(c)])

with open('test.txt') as f:
    read = csv.reader(f)
    for row in read:
        for element in row:
            print remove_accents(element)

Le résultat :

Montreal
uber
12.89
Mere
Francoise
noel
889

(Note : Je suis sur Mac OS X 10.8.4 et j'utilise Python 2.7.3)

1 votes

remove_accents était destiné à supprimer les accents d'une chaîne unicode. Si on lui passe une chaîne d'octets, il essaye de la convertir en une chaîne unicode avec unicode(input_str) . Cela utilise l'encodage par défaut de python, qui est "ascii". Puisque votre fichier est encodé en UTF-8, cela échouerait. Les lignes 2 et 3 changent l'encodage par défaut de python en UTF-8, et cela fonctionne, comme vous l'avez découvert. Une autre option consiste à passer remove_accents une chaîne unicode : supprimez les lignes 2 et 3, et sur la dernière ligne remplacez element par element.decode("utf-8") . J'ai testé : cela fonctionne. Je vais mettre à jour ma réponse pour que ce soit plus clair.

0 votes

Joli montage, bon point. (Dans un autre ordre d'idées : le vrai problème dont je me suis rendu compte est que mon fichier de données est apparemment codé en format iso-8859-1 (que je n'arrive pas à faire fonctionner avec cette fonction, malheureusement !)

0 votes

Aseagram : remplacez simplement "utf-8" par "iso-8859-1", et cela devrait fonctionner. Si vous êtes sous Windows, vous devriez probablement utiliser "cp1252" à la place.

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