¿Cuál es la mejor manera de eliminar acentos en una cadena Unicode de Python?

507

Tengo una cadena Unicode en Python, y me gustaría eliminar todos los acentos (signos diacríticos).

Encontré en la Web una forma elegante de hacer esto en Java:

  1. Convierta la cadena Unicode a su forma normalizada larga (con un carácter separado para letras y diacríticos)
  2. elimine todos los caracteres cuyo tipo Unicode sea "diacrítico".

¿Necesito instalar una biblioteca como pyICU o es posible solo 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 caracteres acentuados a su contraparte no acentuada.

MiniQuark
fuente

Respuestas:

448

Unidecode es la respuesta correcta para esto. Translitera 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'
Christian Oudard
fuente
67
Parece funcionar 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.
Eric O Lebigot
10
depende de lo que intentes lograr. por ejemplo, estoy haciendo una búsqueda en este momento, y no quiero transcribir griego / ruso / chino, solo quiero reemplazar "ą / ę / ś / ć" con "a / e / s / c"
kolinko
58
@EOL unidecode funciona muy bien para cadenas como "François", si le pasa objetos unicode. Parece que lo intentaste con una cadena de bytes simple.
Karl Bartel
26
Tenga en cuenta que unidecode> = 0.04.10 (diciembre de 2012) es GPL. Utilice versiones anteriores o consulte github.com/kmike/text-unidecode si necesita una licencia más permisiva y puede soportar una implementación ligeramente peor.
Mikhail Korobov
10
unidecodereemplaza °con deg. Hace más que solo eliminar acentos.
Eric Duminil
274

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 también funciona en letras griegas:

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

La categoría de caracteres "Mn" significa Nonspacing_Mark, que es similar a unicodedata.combining en la respuesta de MiniQuark (no pensé en unicodedata.combining, pero probablemente sea la mejor solución, porque es más explícita).

Y tenga en cuenta que estas manipulaciones pueden alterar significativamente el significado del texto. Los acentos, Umlauts, etc. no son "decoración".

oefe
fuente
66
Desafortunadamente, estos no son caracteres compuestos, ¡aunque "ł" se llama "LETRA LATINA PEQUEÑA L CON CARRERA"! Necesitarás jugar juegos con análisis unicodedata.name, o desglosar y usar una tabla similar, lo que necesitarías para las letras griegas de todos modos (Α es simplemente "ALFA GRÁFICA LETRA DE CAPITAL").
alexis
2
@andi, me temo que no puedo adivinar qué punto quieres hacer. El intercambio de correo electrónico refleja lo que escribí anteriormente: Debido a que la letra "ł" no es una letra acentuada (y no se trata como una en el estándar Unicode), no tiene una descomposición.
alexis
2
@alexis (seguimiento tardío): esto también funciona perfectamente para el griego, por ejemplo. La "ALFA GRÁFICA DE LA LETRA DE CAPITAL CON DASIA Y VARIA" se normaliza en "ALFA GRÁFICA DE LA LETRA DE CAPITAL" tal como se esperaba. A menos que usted se refiere a la transcripción , que no es lo mismo que "la eliminación de acentos" ... (por ejemplo, "α" → "a".)
Lenz
@lenz, no estaba hablando de eliminar acentos del griego, sino del "trazo" en el codo. Como no es un signo diacrítico, cambiarlo a ell simple es lo mismo que cambiar el Alfa griego a A. Si no lo quiere, no lo haga, pero en ambos casos está sustituyendo un parecido latino (cercano).
alexis
Principalmente funciona bien :) Pero no se transforma ßen ascii, sspor ejemplo. Todavía lo usaría unidecodepara evitar accidentes.
Arte
147

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 francés, por ejemplo), pero creo que el segundo paso (eliminar los acentos) podría manejarse mejor que soltar los caracteres que no son ASCII, porque esto fallará en algunos idiomas (griego, por ejemplo). La mejor solución probablemente sería eliminar explícitamente los caracteres unicode que están etiquetados como diacríticos.

Editar : esto hace 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á verdadero si el carácter cse puede combinar con el carácter anterior, esto es principalmente si es un signo diacrítico.

Edición 2 : remove_accentsespera una cadena unicode , no una cadena de bytes. Si tiene una cadena de bytes, debe decodificarla en una cadena unicode como esta:

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)
MiniQuark
fuente
55
Tuve que agregar 'utf8' a Unicode:nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))
Jabba
@Jabba: , 'utf8'es una "red de seguridad" necesaria si está probando la entrada en la terminal (que por defecto no usa unicode). Pero generalmente no tiene que agregarlo, ya que si está eliminando acentos, input_stres muy probable que ya sea utf8. Sin embargo, no está de más estar a salvo.
MestreLion
1
@rbp: debe pasar una cadena Unicode a remove_accentsuna cadena normal (u "é" en lugar de "é"). Pasó una cadena normal a remove_accents, por lo que al intentar convertir su cadena en una cadena Unicode, asciise utilizó la codificación predeterminada . Esta codificación no admite ningún byte cuyo valor sea> 127. Cuando escribiste "é" en tu shell, tu sistema operativo lo codificó, probablemente con UTF-8 o alguna codificación de página de códigos de Windows, y eso incluyó bytes> 127. Cambiaré mi función para eliminar la conversión a Unicode: bombardeará más claramente si se pasa una cadena no Unicode.
MiniQuark
1
@MiniQuark que funcionó perfectamente >>> remove_accents (unicode ('é'))
rbp
1
Esta respuesta me dio el mejor resultado en un gran conjunto de datos, la única excepción es "ð": ¡unicodedata no lo tocaría!
s29
43

En realidad trabajo en proyectos compatibles con Python 2.6, 2.7 y 3.4 y tengo que crear ID 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'
hexaJer
fuente
2
Con Py2.7, pasar un error de cadena ya unicode en text = unicode(text, 'utf-8'). Una solución para eso fue agregarexcept TypeError: pass
Daniel Reis
Muy ruidoso! Trabajó en mi caso. Uma seleção de poesia brasileira para desenvolver a capacidade de escuta dos alunos idioma Português.
Aaron
23

Esto maneja no solo acentos, sino también "trazos" (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(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

Esta es la forma más elegante que se me ocurre (y alexis lo ha mencionado en un comentario en esta página), aunque no creo que sea muy elegante. De hecho, es más un truco, como se señala en los comentarios, ya que los nombres Unicode son, en realidad solo nombres, no dan garantía de ser coherentes ni nada.

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

EDITAR NOTA:

Sugerencias incorporadas de los comentarios (manejo de errores de búsqueda, código Python-3).

lenz
fuente
8
Debe atrapar la excepción si el nuevo símbolo no existe. Por ejemplo, hay CUADRADO CON RELLENO VERTICAL ▥, pero no hay CUADRADO. (sin mencionar que este código transforma PARAGUAS CON GOTAS DE LLUVIA ☔ en PARAGUAS ☂).
janek37
Esto se ve elegante al aprovechar las descripciones semánticas de los personajes que están disponibles. ¿Realmente necesitamos la unicodellamada de función allí con Python 3? Creo que una expresión regular más estricta en lugar de la findevitaría todos los problemas mencionados en el comentario anterior, y también, la memorización ayudaría al rendimiento cuando se trata de una ruta de código crítica.
matanster
1
@matanster no, esta es una vieja respuesta de la era Python-2; el tipo de unicodeletra ya no es apropiado en Python 3. En cualquier caso, en mi experiencia no existe una solución universal y elegante para este problema. Dependiendo de la aplicación, cualquier enfoque tiene sus ventajas y desventajas. Herramientas prósperas como la calidad unidecodese basan en tablas hechas a mano. Unicode proporciona algunos recursos (tablas, algoritmos), por ejemplo. por cotejo.
lenz
1
Solo repito, lo que está arriba (py3): 1) unicode (char) -> char 2) try: return ud.lookup (desc) excepto KeyError: return char
mirek
@mirek tienes razón: dado que este hilo es tan popular, esta respuesta merece alguna actualización / mejora. Lo edité
lenz
15

En respuesta a la respuesta de @ MiniQuark:

Intenté leer en un archivo csv que era medio francés (con acentos) y también algunas cadenas que eventualmente se convertirían en enteros y flotantes. Como prueba, creé un test.txtarchivo que se veía así:

Montreal, über, 12.89, Mère, Françoise, noël, 889

Tuve que incluir líneas 2y 3hacerlo funcionar (que encontré en un ticket 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 estoy usando Python 2.7.3)

aseagrama
fuente
1
remove_accentsestaba destinado a eliminar acentos de una cadena unicode. En caso de que pase una cadena de bytes, intenta convertirla en una cadena unicode con unicode(input_str). Utiliza la codificación predeterminada de python, que es "ascii". Como su archivo está codificado con UTF-8, esto fallará. Las líneas 2 y 3 cambian la codificación predeterminada de Python a UTF-8, por lo que funciona, como descubrió. Otra opción es pasar remove_accentsuna cadena Unicode: eliminar las líneas 2 y 3, y en la última línea reemplazar elementpor element.decode("utf-8"). Probé: funciona. Actualizaré mi respuesta para aclarar esto.
MiniQuark
Buena edición, buen punto. (En otra nota: El verdadero problema me he dado cuenta es que mi archivo de datos se codifica en apariencia iso-8859-1, que no puedo ir a trabajar con esta función, por desgracia!)
aseagram
aseagram: simplemente reemplace "utf-8" con "iso-8859-1", y debería funcionar. Si está en Windows, entonces probablemente debería usar "cp1252" en su lugar.
MiniQuark
Por cierto, reload(sys); sys.setdefaultencoding("utf-8")es un truco dudoso a veces recomendado para sistemas Windows; ver stackoverflow.com/questions/28657010/… para más detalles.
PM 2Ring
14

gensim.utils.deaccent (texto) de Gensim - modelado de temas para humanos :

'Sef chomutovskych komunistu dostal postou bily prasek'

Otra solución es unidecode .

Tenga en cuenta que la solución sugerida con unicodedata generalmente elimina los acentos solo en algún carácter (por ejemplo, se convierte 'ł'en '', en lugar de en 'l').

Piotr Migdal
fuente
1
deaccenttodavía da en łlugar de l.
lcieslak
No necesita instalar NumPyy SciPyeliminar acentos.
Nuno André
gracias por la referencia gensim! ¿Cómo se compara con unidecode (en términos de velocidad o precisión)?
Etienne Kintzler
3

Algunos idiomas tienen diacríticos combinables como letras de idiomas y diacríticos de acento para especificar el acento.

Creo que es más seguro especificar explícitamente qué diactrics desea eliminar:

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))
sirex
fuente