¿Cómo consigo que str.translate trabaje con cadenas Unicode?

Tengo el siguiente código:

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) 

Lo que funciona muy bien para cadenas no Unicode:

 >>> translate_non_alphanumerics('!') '_foo__' 

Pero falla para las cadenas de Unicode:

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

No puedo entender el párrafo en “Objetos Unicode” en los documentos de Python 2.6.2 para el método str.translate ().

¿Cómo puedo hacer este trabajo para cadenas de Unicode?

La versión Unicode de translate requiere una asignación de los ordinales de Unicode (que puede recuperar para un solo carácter con ord ) a los ordinales de Unicode. Si desea eliminar caracteres, asigne a None .

Cambié tu función para construir un dict que asigne el ordinal de cada personaje al ordinal de lo que quieres traducir:

 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'!') u'_foo__' 

editar: Resulta que la asignación de traducción debe asignarse desde el ordinal de Unicode (a través de ord ) a otro ordinal de Unicode, una cadena de Unicode o Ninguno (para eliminar). Por lo tanto, he cambiado el valor predeterminado para translate_to para que sea un literal Unicode. Por ejemplo:

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

En esta versión usted puede hacer relativamente cartas a otros

 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) 

Se me ocurrió la siguiente combinación de mi función original y la versión de Mike que funciona con cadenas Unicode y 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) 

Actualización : “forzado” translate_to a unicode para unicode translate_table . Gracias mike

Para un truco simple que funcione en objetos str y unicode, convierta la tabla de traducción a unicode antes de ejecutar 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) 

El problema aquí es que convertirá implícitamente todos los objetos str a unicode, generando errores si to_translate contiene caracteres que no son ascii.

En lugar de tener que especificar todos los caracteres que necesitan ser reemplazados, también podría verlo al revés y, en su lugar, especificar solo los caracteres válidos, de este modo:

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

Esto funciona tanto con unicode como con cadenas normales, y conserva el tipo (si el carácter de replacement_character y la source son del mismo tipo, obviamente).

Encontré que donde en Python 2.7, con el tipo str , escribirías

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

mientras que con unicode tipo dirías

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

En Python 3.6 escribirías

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

tal vez esto es útil.