web-dev-qa-db-fra.com

Comment faire fonctionner str.translate 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 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 ne peux pas comprendre le paragraphe "Objets Unicode" dans la documentation Python 2.6.2 pour la méthode str.translate ().

Comment puis-je faire ce travail pour les chaînes Unicode?

56
Daryl Spitzer

La version Unicode de translate nécessite un mappage d'ordinaux Unicode (que vous pouvez récupérer pour un seul caractère avec ord ) en ordinaux Unicode. Si vous souhaitez supprimer des caractères, vous mappez sur None.

J'ai changé votre fonction pour construire un dict mappant l'ordinal de chaque caractère à 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__'

edit: Il s'avère que le mappage de traduction doit être mappé de l'ordinal Unicode (via ord) sur un autre ordinal Unicode, une chaîne Unicode ou Aucune (à supprimer). J'ai donc changé la valeur par défaut pour translate_to en un littéral Unicode. Par exemple:

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

Dans cette version, vous pouvez faire ses lettres à d’autres 

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)
7
madjardi

J'ai trouvé la combinaison suivante de ma fonction d'origine et de la version de Mike qui fonctionne avec les chaînes 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)

Update: "contraint" translate_to à unicode pour le code unicode translate_table. Merci Mike.

5
Daryl Spitzer

Pour un hack simple qui fonctionnera à la fois sur les objets str et unicode, Convertira 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 convertira implicitement tous les objets str en unicode, en générant des erreurs si to_translate contient des caractères non ascii.

4
eswald

Au lieu d'avoir à spécifier tous les caractères devant être remplacés, vous pouvez également le voir dans l'autre sens et spécifier uniquement les caractères valides, comme suit:

import re

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

    return result

Cela fonctionne avec les chaînes unicode et normales et conserve le type (si le replacement_character et le source sont du même type, évidemment).

0
Claude Précourt

J'ai trouvé que dans python 2.7, avec le type str, vous écririez

import string
table = string.maketrans("123", "abc")
print "135".translate(table)

alors qu'avec le type unicode vous diriez

table = {ord(s): unicode(d) for s, d in Zip("123", "abc")}
print u"135".translate(table)

En python 3.6, vous écririez

table = {ord(s): d for s, d in Zip("123", "abc")}
print("135".translate(table))

c'est peut-être utile.

0
davidav