Python unicode dizesindeki aksanları kaldırmanın en iyi yolu nedir?


507

Python bir Unicode dize var ve tüm aksanları (aksan) kaldırmak istiyorum.

Web'de bunu Java'da yapmanın zarif bir yolunu buldum:

  1. Unicode dizesini normalleştirilmiş uzun biçimine dönüştürün (harfler ve aksan işaretleri için ayrı bir karakterle)
  2. Unicode türü "aksan" olan tüm karakterleri kaldırın.

PyICU gibi bir kütüphane kurmam gerekir mi yoksa bu sadece python standart kütüphanesi ile mümkün mü? Peki ya python 3?

Önemli not: Aksanlı karakterlerden aksanlı olmayan karşılıklarına açık bir eşleme içeren koddan kaçınmak istiyorum.

Yanıtlar:


447

Unidecode bunun için doğru cevaptır. Herhangi bir unicode dizeyi ASCII metninde mümkün olan en yakın temsile dönüştürür.

Misal:

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'

67
Çince ile iyi çalışıyor gibi görünüyor, ama Fransızca adı "François" dönüşüm maalesef daha doğal "Francois" ile karşılaştırıldığında, çok iyi değil "FranASSois" verir.
Eric O Lebigot

10
neyi başarmaya çalıştığınıza bağlıdır. Örneğin, şu anda bir arama yapıyorum ve Yunanca / rusça / çince harf çevirisi yapmak istemiyorum, sadece "ą / ę / ś / ć" yerine "a / e / s / c" koymak istiyorum
kolinko

58
@EOL unidecode, unicode nesneleri iletirseniz "François" gibi dizeler için harika çalışır. Düz bayt dizesiyle denediğiniz anlaşılıyor.
Karl Bartel

26
Unidecode> = 0.04.10 (Aralık 2012) değerinin GPL olduğunu unutmayın. Önceki sürümleri kullanın veya daha izinli bir lisansa ihtiyacınız varsa ve biraz daha kötü bir uygulamaya dayanabiliyorsanız github.com/kmike/text-unidecode adresini kontrol edin .
Mikhail Korobov

10
unidecodeyerini °ile deg. Aksanları kaldırmaktan daha fazlasını yapar.
Eric Duminil

273

Buna ne dersin:

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

Bu Yunan harfleri üzerinde de çalışır:

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

Karakter kategorisi "Mn" anlamına gelir Nonspacing_MarkMiniQuark yanıtında unicodedata.combining benzer olan (I unicodedata.combining düşünmedim ama daha açık olduğu için bu, muhtemelen daha iyi bir çözümdür).

Ve unutmayın, bu manipülasyonlar metnin anlamını önemli ölçüde değiştirebilir. Vurgular, Umlautlar vb. "Dekorasyon" değildir.


6
"Ł" "ZAMANLI LATİN KÜÇÜK MEKTUP L" olarak adlandırılsa da, bunlar maalesef karakterlerden oluşmuyor! Ayrıştırma ile oyunlar oynamanız unicodedata.nameya da benzer bir tablo kullanmanız gerekecek - yine de Yunan harfleri için ihtiyacınız olacak (Α sadece "YUNAN SERMAYE MEKTUBU ALPHA").
alexis

2
@andi, korkarım hangi noktayı yapmak istediğini tahmin edemiyorum. E-posta alışverişi yukarıda yazdıklarımı yansıtıyor: "ł" harfi aksanlı bir harf olmadığından (ve Unicode standardında bir olarak değerlendirilmediğinden), bir ayrışma yok.
alexis

2
@alexis (geç takip): Bu, Yunanca için de iyi sonuç verir - örn. "DASIA VE VARIA İLE YUNAN SERMAYE MEKTUBU ALPHA" beklendiği gibi "YUNAN SERMAYE MEKTUBU ALPHA" olarak normalleştirilmiştir. Eğer atıfta sürece transliterasyonu (örn. "A" → "a") "kaldırma aksan" aynı şey değildir ...
lenz

@lenz, Yunanca aksanları kaldırmaktan değil, eldeki "inme" den bahsettim. Bir aksan olmadığı için, onu düz zil olarak değiştirmek, Yunan Alfa'yı değiştirmekle aynıdır A. Eğer istemiyorsanız, bunu yapmayın, ancak her iki durumda da benzer bir Latin (yakın) görünüşün yerine geçersiniz.
alexis

Çoğunlukla güzel çalışıyor :) Ama örnek ßolarak ascii'ye dönüşmez ss. Hala unidecodekazaları önlemek için kullanırım .
Sanat

146

Bu yanıtı Web’de buldum:

import unicodedata

def remove_accents(input_str):
    nfkd_form = unicodedata.normalize('NFKD', input_str)
    only_ascii = nfkd_form.encode('ASCII', 'ignore')
    return only_ascii

İyi çalışıyor (örneğin Fransızca için), ancak sanırım ikinci adım (aksanları kaldırmak) ASCII olmayan karakterleri bırakmaktan daha iyi işlenebilir, çünkü bu bazı diller için başarısız olacaktır (örneğin, Yunanca). En iyi çözüm muhtemelen aksan olarak etiketlenmiş unicode karakterleri açıkça kaldırmak olacaktır.

Düzenleme : bu hile yapar:

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)karakter cönceki karakterle birleştirilebiliyorsa true değerini döndürür , yani esasen bir aksan işaretiyse.

Edit 2 : bir bayt dizesi değil, remove_accentsbir unicode dizesi bekler . Bir bayt dizeniz varsa, bunun gibi bir unicode dizgiye kodunu çözmeniz gerekir:

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
Unicode'a 'utf8' eklemek zorunda kaldım:nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))
Jabba

@Jabba: , 'utf8'Terminalde girişi test ediyorsanız gerekli olan bir "güvenlik ağıdır" (varsayılan olarak unicode kullanmaz). Ama genellikle yok olması ardından aksan kaldırıyorsanız beri eklemek için input_strçok büyük olasılıkla zaten utf8 olmaktır. Yine de güvenli olmak acıtmaz.
MestreLion

1
@rbp: remove_accentsnormal bir dize yerine bir unicode dize geçirmelisiniz ("é" yerine u "é"). Normal bir dizeyi geçtiniz remove_accents, bu nedenle dizenizi unicode dizeye dönüştürmeye çalışırken varsayılan asciikodlama kullanıldı. Bu kodlama değeri> 127 olan hiçbir baytı desteklemez. Kabuğunuza "é" yazdığınızda, işletim sisteminiz bunu muhtemelen UTF-8 veya bazı Windows Kod Sayfası kodlamasıyla kodladı ve bayt> 127'yi içeriyordu. Unicode dönüşümü kaldırmak için işlevimi değiştireceğim: Unicode olmayan bir dize geçirilirse daha net bombalanır.
MiniQuark

1
@MiniQuark mükemmel çalıştı >>> remove_accents (unicode ('é'))
rbp

1
Bu cevap bana büyük bir veri setinde en iyi sonucu verdi, tek istisna "ð" - unicodedata ona dokunmazdı!
s29

43

Aslında proje uyumlu python 2.6, 2.7 ve 3.4 üzerinde çalışıyorum ve ücretsiz kullanıcı girişlerinden kimlikler oluşturmam gerekiyor.

Sayende harikalar yaratan bu işlevi yarattım.

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

sonuç:

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

2
Py2.7 ile, zaten bir unicode dize hataları at text = unicode(text, 'utf-8'). Bunun için bir çözüm eklemektiexcept TypeError: pass
Daniel Reis

Çok gürültü! Benim durumumda çalıştı. Uma seleção de poesia brasileira para desenvolver bir kapasidade de escuta dos alunos idioma Português.
Aaron

23

Bu sadece aksanları değil, aynı zamanda "vuruşları" da (ø gibi) işler:

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

Bu gerçekten düşünebildiğim en şık yoldur (ve bu sayfadaki bir yorumda alexis tarafından belirtilmiştir), ancak gerçekten çok zarif olduğunu düşünmüyorum. Aslında, Unicode isimleri - gerçekten sadece isimler olduğu için, tutarlılık ya da herhangi bir şey için hiçbir garanti vermedikleri için, yorumlarda belirtildiği gibi, daha çok bir hack'tir.

Unicode adları 'WITH' içermediğinden, bununla işlenmeyen, ters ve ters harfler gibi hala özel harfler vardır. Yine de ne yapmak istediğinize bağlı. Bazen sözlük sıralama düzenine ulaşmak için aksan sıyırma ihtiyacım vardı.

NOTU DÜZENLE:

Yorumlardan gelen öneriler (arama hatalarını işleme, Python-3 kodu).


8
Yeni sembol yoksa istisnayı yakalamanız gerekir. Örneğin DİKEY DOLGU S KARE var, ancak KARE yok. (bu kodun YAĞMUR DAMLALI UMBRELLA'yı UMBRELLA ☔'ya dönüştürdüğünden bahsetmiyorum).
janek37

Bu, mevcut karakterlerin semantik açıklamalarından yararlanmada zarif görünüyor. Gerçekten unicodepython 3 ile fonksiyon çağrısına ihtiyacımız var mı? Bence daha sıkı bir regex yerine findyukarıdaki yorumda belirtilen tüm sorun önlemek ve ayrıca, not kritik bir kod yolu olduğunda performans yardımcı olacaktır.
matanster

1
@matanster no, bu Python-2 döneminden kalma eski bir cevaptır; unicodekalıplaştığı artık bu soruna hiçbir evrensel, zarif bir çözüm yoktur benim deneyim, her durumda Python 3'te uygundur. Uygulamaya bağlı olarak, herhangi bir yaklaşımın artıları ve eksileri vardır. Kaliteli üretim araçları unidecode, el yapımı tablolara dayanmaktadır. Bazı kaynaklar (tablolar, algoritmalar) Unicode tarafından sağlanır, örn. harmanlama için.
lenz

1
Sadece tekrar ediyorum, yukarıda ne var (py3): 1) unicode (char) -> char 2) deneyin: KeyError hariç dönüş ud.lookup (desc): dönüş char
mirek

@mirek haklısın: Bu konu çok popüler olduğu için bu cevap bazı güncellemeleri / iyileştirmeleri hak ediyor. Ben düzenledim.
lenz

15

@ MiniQuark'ın cevabına yanıt olarak:

Ben yarım Fransızca (aksan içeren) ve ayrıca tamsayı ve yüzen olacak bazı dizeleri olan bir csv dosyasında okumaya çalışıyordu. Bir test olarak, test.txtşuna benzer bir dosya oluşturdum :

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

Çizgileri dahil etmek 2ve 3(bir python biletinde bulduğum) işe almak ve @ Jabba'nın yorumunu dahil etmek zorunda kaldım :

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)

Sonuç:

Montreal
uber
12.89
Mere
Francoise
noel
889

(Not: Mac OS X 10.8.4 ve Python 2.7.3 kullanıyorum)


1
remove_accentsbir unicode dizeden aksanları kaldırmak anlamına geliyordu. Bir bayt-dizesi geçmesi durumunda, onu bir unicode dizgeye dönüştürmeye çalışır unicode(input_str). Bu, python'un "ascii" olan varsayılan kodlamasını kullanır. Dosyanız UTF-8 ile kodlandığından, bu başarısız olur. Satır 2 ve 3, python'un varsayılan kodlamasını UTF-8 olarak değiştirir, böylece öğrendiğiniz gibi çalışır. Başka bir seçenek geçmektir remove_accentsunicode dizesi: remove hat 2 ve 3, ve son satıra değiştirin elementtarafından element.decode("utf-8"). Test ettim: işe yarıyor. Bunu daha net hale getirmek için cevabımı güncelleyeceğim.
MiniQuark

Güzel düzenleme, iyi bir nokta. (Başka bir notta: iso-8859-1
Fark

aseagram: "utf-8" i "iso-8859-1" ile değiştirin ve çalışması gerekir. Windows kullanıyorsanız, muhtemelen "cp1252" kullanmalısınız.
MiniQuark

BTW, reload(sys); sys.setdefaultencoding("utf-8")bazen Windows sistemleri için önerilen şüpheli bir hack'tir; ayrıntılar için stackoverflow.com/questions/28657010/… adresine bakın.
PM 2Ring

14

gensim.utils.deaccent (metin) den Gensim - insanlar için başlık modelleme :

'Sef chomutovskych komunistu dostal postou bily prasek'

Başka bir çözüm unidecode .

İle önerilen çözüm olduğunu Not unicodedata tipik (örneğin o döner sadece bazı karakter aksan kaldırır 'ł'içine ''ziyade içine daha 'l').


1
deaccenthala łyerine veriyor l.
lcieslak

Aksanları kurmanıza NumPyve SciPykaldırmanıza gerek yoktur .
Nuno André

gensim referans için teşekkürler! unidecode ile nasıl karşılaştırılır (hız veya doğruluk açısından)?
Etienne Kintzler

3

Bazı diller aksanı belirtmek için aksan harflerini dil harfleri ve aksan aksanları olarak birleştirir.

Ben hangi diactrics şerit istediğini açıkça belirtmek daha güvenli olduğunu düşünüyorum:

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))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.