2009-02-05 47 views
299

Tengo una cadena Unicode en Python, y me gustaría eliminar todos los acentos (signos diacríticos).¿Cuál es la mejor manera de eliminar acentos en una cadena unicode de Python?

me encontré en la web una manera elegante de hacer esto en Java:

  1. convertir la cadena Unicode a su forma normalizada de longitud (con un carácter independiente para las letras y los signos diacríticos)
  2. eliminar todos los caracteres cuyo tipo Unicode es "diacrítico".

¿Debo instalar una biblioteca como pyICU o es posible con la biblioteca estándar de python? ¿Y qué hay de Python 3?

Nota importante: me gustaría evitar el código con una asignación explícita de los caracteres acentuados a su contraparte no acentuada.

Respuesta

230

Unidecode es la respuesta correcta para esto. Transcribe cualquier cadena Unicode en la representación más cercana posible en texto ascii.

Ejemplo:

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

Sí, esta es una mejor solución que simplemente quitar los acentos. Proporciona transliteraciones mucho más útiles para los lenguajes que tienen convenciones para escribir palabras en ASCII. –

+34

Parece que funciona bien con los chinos, pero la transformación del nombre francés "François" desafortunadamente da "FranASSois", que no es muy bueno, en comparación con el "Francois" más natural. – EOL

+8

depende de lo que intentes lograr. por ejemplo, estoy haciendo una búsqueda ahora, y no quiero transcribir el griego/ruso/chino, solo quiero reemplazar "ą/ę/ś/ć" por "a/e/s/c" – kolinko

112

acabo de encontrar esta respuesta en la 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 

Funciona bien (para el francés, por ejemplo), pero creo que el segundo paso (eliminación de los acentos) podría manejarse mejor que dejar caer la no -caracteres ASCII, porque esto fallará para algunos idiomas (griego, por ejemplo). La mejor solución sería eliminar explícitamente los caracteres Unicode etiquetados como signos diacríticos.

Editar: este es el truco:

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) devolverá true si el personaje c se puede combinar con el carácter anterior, es decir, principalmente si se trata de una diacrítica.

Editar 2: remove_accents espera una cadena Unicode, no una cadena de bytes. Si usted tiene una cadena de bytes, entonces debe decodificar en una cadena Unicode como esto:

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) 
+5

Tuve que agregar 'utf8' a unicode: 'nkfd_form = unicodedata.normalize ('NFKD', unicode (input_str, 'utf8'))' – Jabba

+0

@Jabba: ', 'utf8'' es una" red de seguridad " necesario si está probando la entrada en el terminal (que de forma predeterminada no utiliza unicode). Pero normalmente no * tiene * que agregar, ya que si está eliminando acentos, es probable que 'input_str' ya sea utf8. Sin embargo, no está de más ser seguro. – MestreLion

+0

>>> def remove_accents (input_str): ... nkfd_form = unicodedata.normalize ('NFKD', unicode (input_str)) ... return u "". Join ([c para c en nkfd_form si no es unicodedata. la combinación (c)]) ... >>> remove_accents() Rastreo (llamada más reciente 'e' última): archivo "", línea 1, en archivo "", línea 2, en remove_accents UnicodeDecodeError: el códec 'ascii' no puede decodificar el byte 0xc3 en la posición 0: ordinal no en el rango (128) – rbp

211

¿Qué tal esto:

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

Esto funciona en letras griegas, también:

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

character category "Mn" significa Nonspacing_Mark, que es similar a unicodedata.combining en la respuesta de MiniQuark (no pensé en unicodedata.combining, pero probablemente es la mejor solución encendido, porque es más explícito).

Y tenga en cuenta que estas manipulaciones pueden alterar significativamente el significado del texto. Acentos, diéresis, etc. no son "decoración".

+5

Desafortunadamente, estos caracteres no están compuestos, ¡aunque "ł" se llame "LETRA L MINÚSCULA LATINA CON GOLPES"! Necesitarás jugar con el análisis 'unicodedata.name', o desglosar y usar una tabla similar, que de todos modos necesitarías para las letras griegas (Α es solo" LETRA GRIEGA ALPHA LETRA "). – alexis

+0

@alexis https://mail.python.org/pipermail/python-list/2007-October/446440.html – andi

+0

@andi, me temo que no puedo adivinar qué punto desea hacer. El intercambio de correos electrónicos refleja lo que escribí arriba: como la letra "³" no es una letra acentuada (y no se trata como una en el estándar Unicode), no tiene una descomposición. – alexis

11

Esto no sólo se encarga de acentos, pero también "golpes" (como en ø 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(unicode(char)) 
    cutoff = desc.find(' WITH ') 
    if cutoff != -1: 
     desc = desc[:cutoff] 
    return ud.lookup(desc) 

Ésta es la forma más elegante que puedo pensar (y ha sido mencionado por alexis en un comentario en esta página), aunque no creo que sea muy elegante.

Todavía hay letras especiales que no son manejadas por esto, como letras giradas e invertidas, ya que su nombre Unicode no contiene 'CON'. Depende de lo que quieras hacer de todos modos. A veces necesitaba acentos para lograr el orden de clasificación del diccionario.

+4

Debería detectar la excepción si el nuevo símbolo no existe. Por ejemplo, hay CUADRADO CON LLENADO VERTICAL ▥, pero no hay CUADRADO. (sin mencionar que este código transforma UMBRELLA CON RAIN DROPS ☔ en UMBRELLA ☂). – janek37

10

En respuesta a la respuesta de @ MiniQuark:

yo estaba tratando de leer en un archivo CSV que era (que contienen acentos) medio francesa y también algunas cadenas que con el tiempo se convertiría en números enteros y flotadores. Como prueba, he creado un archivo test.txt que se veía así:

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

he tenido que incluir líneas 2 y 3 a conseguir que funcione (que encontré en un billete de Python), así como incorporar @ El comentario 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) 

El resultado:

Montreal 
uber 
12.89 
Mere 
Francoise 
noel 
889 

(Nota: estoy en Mac OS X 10.8.4 y el uso de Python 2.7.3)

+1

'remove_accents' fue diseñado para eliminar acentos de una cadena Unicode. En caso de que pase una cadena de bytes, intenta convertirla a una cadena unicode con 'unicode (input_str)'. Utiliza la codificación por defecto de python, que es "ascii". Como su archivo está codificado con UTF-8, esto fallaría. Las líneas 2 y 3 cambian la codificación predeterminada de Python a UTF-8, entonces funciona, como descubriste. Otra opción es pasar 'remove_accents' una cadena unicode: eliminar las líneas 2 y 3, y en la última línea reemplazar' element' por 'element.decode (" utf-8 ")'. Probé: funciona. Actualizaré mi respuesta para aclarar esto. – MiniQuark

+0

Buena edición, buen punto. (En otra nota: ¡El problema real que me he dado cuenta es que mi archivo de datos aparentemente está codificado en 'iso-8859-1', que por desgracia no puedo trabajar con esta función!) – aseagram

+0

aseagram: simplemente reemplace" utf-8 "con" iso-8859-1 ", y debería funcionar. Si está en Windows, probablemente debería usar "cp1252". – MiniQuark

14

Actualmente trabajo en proyectos compatibles con Python 2.6, 2.7 y 3.4 y tengo que crear identificaciones a partir de entradas de usuario gratuitas.

Gracias a ti, he creado esta función que funciona de maravilla.

import re 
import unicodedata 

def strip_accents(text): 
    """ 
    Strip accents from input String. 

    :param text: The input string. 
    :type text: String. 

    :returns: The processed String. 
    :rtype: String. 
    """ 
    try: 
     text = unicode(text, 'utf-8') 
    except (TypeError, NameError): # unicode is a default on python 3 
     pass 
    text = unicodedata.normalize('NFD', text) 
    text = text.encode('ascii', 'ignore') 
    text = text.decode("utf-8") 
    return str(text) 

def text_to_id(text): 
    """ 
    Convert input text to id. 

    :param text: The input string. 
    :type text: String. 

    :returns: The processed String. 
    :rtype: String. 
    """ 
    text = strip_accents(text.lower()) 
    text = re.sub('[ ]+', '_', text) 
    text = re.sub('[^0-9a-zA-Z_-]', '', text) 
    return text 

resultado:

text_to_id("Montréal, über, 12.89, Mère, Françoise, noël, 889") 
>>> 'montreal_uber_1289_mere_francoise_noel_889' 
+0

cadena unicode con python3: http://stackoverflow.com/a/6812069/1569144 – Jer42

+2

Con Py2.7, pasando un error de cadena ya unicode en 'text = unicode (text, 'utf-8')'. Una solución para eso fue agregar 'excepto TypeError: pase' –

+0

No sé qué es, pero funciona (Y) –

0

Algunas lenguas han combinar diacríticos como cartas de lenguaje y signos diacríticos del acento para especificar acento.

creo que es más seguro especificar explícitamente lo diactrics desea despojar a:

def strip_accents(string, accents=('COMBINING ACUTE ACCENT', 'COMBINING GRAVE ACCENT', 'COMBINING TILDE')): 
    accents = set(map(unicodedata.lookup, accents)) 
    chars = [c for c in unicodedata.normalize('NFD', string) if c not in accents] 
    return unicodedata.normalize('NFC', ''.join(chars)) 
6
import unicodedata 
s = 'Émission' 
search_string = ''.join((c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn')) 

Para Python 3.x

print (search_string) 

Para Python 2.X

print search_string 
+2

Esto parece funcionar bien. 'unidecode' traduce' ° 'a' deg', que podría no ser el comportamiento deseado. –

0

gensim.utils.deaccent(text) de Gensim - topic modelling for humans:

deaccent("Šéf chomutovských komunistů dostal poštou bílý prášek") 'Sef chomutovskych komunistu dostal postou bily prasek'

Otra solución es unidecode.

No es que la solución sugerida con unicodedata normalmente elimina los acentos sólo en algunos caracteres (por ejemplo, se convierte en 'ł''', en lugar de en 'l').

Cuestiones relacionadas