56 votes

Comment faire pour que str.translate fonctionne avec les chaînes Unicode ?

J'ai le code suivant :

import string
def translate_non_alphanumerics(to_translate, translate_to='_'):
    not_letters_or_digits = u'!"#%\'()*+,-./:;<=>?@[\]^_`{|}~'
    translate_table = string.maketrans(not_letters_or_digits,
                                       translate_to
                                         *len(not_letters_or_digits))
    return to_translate.translate(translate_table)

Ce qui fonctionne très bien pour les chaînes de caractères non unicode :

>>> translate_non_alphanumerics('<foo>!')
'_foo__'

Mais échoue pour les chaînes unicode :

>>> translate_non_alphanumerics(u'<foo>!')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in translate_non_alphanumerics
TypeError: character mapping must return integer, None or unicode

Je n'arrive pas à comprendre le paragraphe sur les "objets Unicode" dans le document Documentation sur Python 2.6.2 pour la méthode str.translate().

Comment faire pour que cela fonctionne pour les chaînes Unicode ?

0 votes

Meilleure utilisation import string; string.punctuation au lieu de coder en dur not_letters_or_digits en code réel. Je comprends qu'ici vous préférez être explicite.

57voto

Mike Boers Points 3999

La version Unicode de translate nécessite une mise en correspondance des ordinaux Unicode (que vous pouvez récupérer pour un seul caractère avec ord ) en ordinaux Unicode. Si vous voulez supprimer des caractères, vous faites une correspondance avec None .

J'ai modifié votre fonction pour construire un dict qui met en correspondance l'ordinal de chaque caractère avec l'ordinal de ce que vous voulez traduire :

def translate_non_alphanumerics(to_translate, translate_to=u'_'):
    not_letters_or_digits = u'!"#%\'()*+,-./:;<=>?@[\]^_`{|}~'
    translate_table = dict((ord(char), translate_to) for char in not_letters_or_digits)
    return to_translate.translate(translate_table)

>>> translate_non_alphanumerics(u'<foo>!')
u'_foo__'

éditer : Il s'avère que le mappage de traduction doit passer de l'ordinal Unicode (via ord ) vers un autre ordinal Unicode, une chaîne Unicode, ou None (pour supprimer). J'ai donc modifié la valeur par défaut de translate_to pour être un littéral Unicode. Par exemple :

>>> translate_non_alphanumerics(u'<foo>!', u'bad')
u'badfoobadbad'

11 votes

Merci ! (Une décision de conception si stupide d'avoir une fonction au nom identique qui fonctionne différemment).

1 votes

Aussi, si vous ne voulez pas définir manuellement les caractères de ponctuation : import string ; translate_table = {ord(unicode(c)) for c in string.punctuation} Note : Ceci ne traduira pas tous les caractères spéciaux de ponctuation unicode (il y en a des tonnes...)

0 votes

Votre not_letters_or_digits est dépourvue de "$" et de "&". Je vous propose d'utiliser string.punctuation au lieu de coder en dur l'ensemble ou les caractères

7voto

madjardi Points 475

Dans cette version, vous pouvez faire des lettres à d'autres personnes de manière relative.

def trans(to_translate):
    tabin = u'привет'
    tabout = u'тевирп'
    tabin = [ord(char) for char in tabin]
    translate_table = dict(zip(tabin, tabout))
    return to_translate.translate(translate_table)

5voto

Daryl Spitzer Points 18304

J'ai trouvé la combinaison suivante de ma fonction originale et de la fonction Mike qui fonctionne avec les chaînes de caractères Unicode et ASCII :

def translate_non_alphanumerics(to_translate, translate_to=u'_'):
    not_letters_or_digits = u'!"#%\'()*+,-./:;<=>?@[\]^_`{|}~'
    if isinstance(to_translate, unicode):
        translate_table = dict((ord(char), unicode(translate_to))
                               for char in not_letters_or_digits)
    else:
        assert isinstance(to_translate, str)
        translate_table = string.maketrans(not_letters_or_digits,
                                           translate_to
                                              *len(not_letters_or_digits))
    return to_translate.translate(translate_table)

Mise à jour : "contraint" translate_to à l'unicode pour l'unicode translate_table . Merci Mike.

0 votes

Je suggère que vous contraigniez le translate_to en Unicode pour la version Unicode, sinon l'appel translate va paniquer si vous lui passez une chaîne Unicode et une chaîne "normale".

0 votes

Cela semble être quelque chose qui devrait faire partie de la langue. +1

4voto

eswald Points 3784

Pour un hack simple qui fonctionnera sur les objets str et unicode, convertissez la table de traduction en unicode avant d'exécuter translate() :

import string
def translate_non_alphanumerics(to_translate, translate_to='_'):
    not_letters_or_digits = u'!"#%\'()*+,-./:;<=>?@[\]^_`{|}~'
    translate_table = string.maketrans(not_letters_or_digits,
                                       translate_to
                                         *len(not_letters_or_digits))
    translate_table = translate_table.decode("latin-1")
    return to_translate.translate(translate_table)

Le problème ici est qu'il va implicitement convertir tous les objets str en unicode, en lançant des erreurs si to_translate contient des caractères non-ascii.

0voto

Claude Précourt Points 443

Au lieu de devoir spécifier tous les caractères qui doivent être remplacés, vous pouvez également envisager l'inverse et, à la place, ne spécifier que les caractères valides, comme ceci :

import re

def replace_non_alphanumerics(source, replacement_character='_'):
    result = re.sub("[^_a-zA-Z0-9]", replacement_character, source)

    return result

Cela fonctionne aussi bien avec l'unicode qu'avec les chaînes de caractères normales, et préserve le type (si les deux paramètres de l'option replacement_character et le source sont du même type, évidemment).

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