Apa cara terbaik untuk menghilangkan aksen dalam string unicode Python?

507

Saya memiliki string Unicode dengan Python, dan saya ingin menghapus semua aksen (diakritik).

Saya temukan di Web cara yang elegan untuk melakukan ini di Jawa:

  1. mengonversi string Unicode ke bentuk normalnya yang panjang (dengan karakter terpisah untuk huruf dan diakritik)
  2. hapus semua karakter yang jenis Unicode-nya "diakritik".

Apakah saya perlu menginstal perpustakaan seperti pyICU atau apakah ini mungkin hanya dengan perpustakaan standar python? Dan bagaimana dengan python 3?

Catatan penting: Saya ingin menghindari kode dengan pemetaan eksplisit dari karakter beraksen ke mitra non-aksen mereka.

MiniQuark
sumber

Jawaban:

448

Unidecode adalah jawaban yang benar untuk ini. Ini mentransliterasi setiap string unicode ke representasi terdekat yang mungkin dalam teks ascii.

Contoh:

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
sumber
67
Tampaknya bekerja dengan baik dengan Cina, tetapi transformasi dari nama Perancis "François" sayangnya memberi "FranASSois", yang tidak terlalu baik, dibandingkan dengan "Francois" yang lebih alami.
Eric O Lebigot
10
tergantung apa yang Anda coba capai. misalnya saya sedang melakukan pencarian sekarang, dan saya tidak ingin transliterasi bahasa Yunani / Rusia / Mandarin, saya hanya ingin mengganti "ą / ę / ś / ć" dengan "a / e / s / c"
kolinko
58
@ EOL unidecode berfungsi bagus untuk string seperti "François", jika Anda mengirimkan objek unicode ke sana. Sepertinya Anda mencoba dengan string byte polos.
Karl Bartel
26
Perhatikan bahwa unidecode> = 0,04.10 (Des 2012) adalah GPL. Gunakan versi sebelumnya atau periksa github.com/kmike/text-unidecode jika Anda memerlukan lisensi yang lebih permisif dan dapat bertahan dengan implementasi yang sedikit lebih buruk.
Mikhail Korobov
10
unidecodemenggantikan °dengan deg. Itu lebih dari sekadar menghilangkan aksen.
Eric Duminil
274

Bagaimana dengan ini:

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

Ini juga berfungsi pada huruf yunani:

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

The kategori karakter "Mn" singkatan Nonspacing_Mark, yang mirip dengan unicodedata.combining dalam jawaban MiniQuark (saya tidak memikirkan unicodedata.combining, tetapi mungkin adalah solusi yang lebih baik, karena lebih eksplisit).

Dan perlu diingat, manipulasi ini dapat secara signifikan mengubah makna teks. Aksen, Umlaut, dll. Bukan "hiasan".

Oefe
sumber
6
Sayangnya, ini bukan karakter yang dikomposisikan - meskipun "ł" dinamai "LATIN KECIL SURAT L DENGAN STROKE"! Anda harus bermain gim dengan parsing unicodedata.name, atau mendobrak dan menggunakan meja yang mirip - yang Anda perlukan untuk huruf-huruf Yunani (Α hanyalah "GREAT CAPITAL LETTER ALPHA").
alexis
2
@andi, saya khawatir saya tidak bisa menebak poin apa yang ingin Anda sampaikan. Pertukaran email mencerminkan apa yang saya tulis di atas: Karena huruf "ł" bukan huruf beraksen (dan tidak diperlakukan sebagai satu dalam standar Unicode), itu tidak memiliki dekomposisi.
alexis
2
@alexis (tindak lanjut lanjut): Ini juga berfungsi dengan baik untuk bahasa Yunani - misalnya. "ALPHA SURAT MODAL YUNANI DENGAN DASIA DAN VARIA" dinormalisasi menjadi "SURAT ALAMI MODAL YUNANI" seperti yang diharapkan. Kecuali jika Anda mengacu pada transliterasi (mis. "Α" → "a"), yang tidak sama dengan "menghilangkan aksen" ...
lenz
@ Lenz, saya tidak berbicara tentang menghilangkan aksen dari bahasa Yunani, tetapi tentang "pukulan" pada ell. Karena itu bukan diakritik, mengubahnya menjadi ell polos sama dengan mengubah Alpha Yunani ke A. Jika tidak mau, jangan lakukan itu, tetapi dalam kedua kasus Anda mengganti bahasa Latin (hampir sama).
Alex
Sebagian besar berfungsi dengan baik :) Tapi itu tidak berubah ßmenjadi ascii sssebagai contoh. Saya masih akan menggunakan unidecodeuntuk menghindari kecelakaan.
Seni
146

Saya baru saja menemukan jawaban ini di 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

Ini berfungsi dengan baik (untuk Perancis, misalnya), tapi saya pikir langkah kedua (menghapus aksen) dapat ditangani lebih baik daripada menjatuhkan karakter non-ASCII, karena ini akan gagal untuk beberapa bahasa (Yunani, misalnya). Solusi terbaik mungkin akan secara eksplisit menghapus karakter unicode yang ditandai sebagai diakritik.

Sunting : ini berguna:

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)akan mengembalikan true jika karakter cdapat dikombinasikan dengan karakter sebelumnya, terutama jika itu diakritik.

Sunting 2 : remove_accentsmengharapkan string unicode , bukan string byte. Jika Anda memiliki string byte, maka Anda harus mendekodekannya menjadi string unicode seperti ini:

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
sumber
5
Saya harus menambahkan 'utf8' ke unicode:nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))
Jabba
@ Jabba: , 'utf8'adalah "jaring pengaman" yang diperlukan jika Anda menguji input di terminal (yang secara default tidak menggunakan unicode). Tetapi biasanya Anda tidak perlu menambahkannya, karena jika Anda menghapus aksen maka input_strsangat mungkin utf8. Tidak ada ruginya untuk aman.
MestreLion
1
@ rbp: Anda harus meneruskan string unicode remove_accentsalih-alih string biasa (u "é" bukannya "é"). Anda meneruskan string biasa ke remove_accents, jadi ketika mencoba mengkonversi string Anda ke string unicode, asciipengkodean default digunakan. Pengkodean ini tidak mendukung byte yang nilainya> 127. Ketika Anda mengetik "é" di shell Anda, OS Anda menyandikannya, mungkin dengan UTF-8 atau beberapa pengkodean Halaman Kode Windows, dan itu termasuk byte> 127. Saya akan mengubah fungsi saya untuk menghapus konversi ke unicode: itu akan mengebom lebih jelas jika string non-unicode dilewatkan.
MiniQuark
1
@MiniQuark yang berfungsi dengan baik >>> remove_accents (unicode ('é'))
rbp
1
Jawaban ini memberi saya hasil terbaik pada kumpulan data besar, satu-satunya pengecualian adalah "ð" - unicodedata tidak akan menyentuhnya!
s29
43

Sebenarnya saya bekerja pada proyek yang kompatibel dengan python 2.6, 2.7 dan 3.4 dan saya harus membuat ID dari entri pengguna gratis.

Terima kasih kepada Anda, saya telah membuat fungsi ini bekerja sangat baik.

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

hasil:

text_to_id("Montréal, über, 12.89, Mère, Françoise, noël, 889")
>>> 'montreal_uber_1289_mere_francoise_noel_889'
heksaJer
sumber
2
Dengan Py2.7, melewatkan kesalahan string yang sudah unicode di text = unicode(text, 'utf-8'). Solusi untuk itu adalah menambahkanexcept TypeError: pass
Daniel Reis
Sangat noice! Bekerja dalam kasus saya. Anda dapat menggunakan salah satu dari daftar ini untuk memilih kapasitor terbaik dari alun-alun idioma Português.
Aaron
23

Ini tidak hanya menangani aksen, tetapi juga "guratan" (seperti pada ø dll.):

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

Ini adalah cara paling elegan yang dapat saya pikirkan (dan telah disebutkan oleh alexis dalam komentar di halaman ini), walaupun saya pikir itu tidak terlalu elegan. Sebenarnya, ini lebih merupakan peretasan, seperti yang ditunjukkan dalam komentar, karena nama Unicode - benar-benar hanya nama, mereka tidak memberikan jaminan untuk konsisten atau apa pun.

Masih ada surat khusus yang tidak ditangani oleh ini, seperti surat yang dibalik dan terbalik, karena nama unicode mereka tidak mengandung 'WITH'. Tergantung pada apa yang ingin Anda lakukan. Saya kadang-kadang membutuhkan aksen stripping untuk mencapai urutan kamus.

CATATAN EDIT:

Memasukkan saran dari komentar (menangani kesalahan pencarian, kode Python-3).

lenz
sumber
8
Anda harus menangkap pengecualian jika simbol baru tidak ada. Misalnya ada KOTAK DENGAN FILL VERTIKAL ▥, tetapi tidak ada KOTAK. (Belum lagi bahwa kode ini mengubah UMBRELLA DENGAN DROPS HUJAN ☔ menjadi UMBRELLA ☂).
janek37
Ini terlihat elegan dalam memanfaatkan deskripsi semantik karakter yang tersedia. Apakah kita benar-benar membutuhkan unicodepemanggilan fungsi di sana dengan python 3? Saya pikir regex yang lebih ketat di tempat findakan menghindari semua masalah yang disebutkan dalam komentar di atas, dan juga, memoisasi akan membantu kinerja ketika itu jalur kode kritis.
matanster
1
@ matanster tidak, ini adalah jawaban lama dari era Python-2; yang unicodetypecast tidak lagi yang sesuai dengan Python 3. Dalam kasus apapun, dalam pengalaman saya tidak ada yang universal, solusi elegan untuk masalah ini. Tergantung pada aplikasi, pendekatan apa pun memiliki pro dan kontra. Alat yang berkembang pesat seperti unidecodedidasarkan pada tabel kerajinan tangan. Beberapa sumber daya (tabel, algoritma) disediakan oleh Unicode, misalnya. untuk pemeriksaan.
lenz
1
Saya ulangi saja, apa yang ada di atas (py3): 1) unicode (char) -> char 2) coba: return ud.lookup (desc) kecuali KeyError: return char
mirek
@mirek Anda benar: karena utas ini sangat populer, jawaban ini patut diperbarui / ditingkatkan. Saya mengeditnya.
lenz
15

Menanggapi jawaban @ MiniQuark:

Saya mencoba membaca dalam file csv yang setengah-Perancis (mengandung aksen) dan juga beberapa string yang akhirnya akan menjadi bilangan bulat dan mengapung. Sebagai ujian, saya membuat test.txtfile yang terlihat seperti ini:

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

Saya harus memasukkan baris 2dan 3membuatnya bekerja (yang saya temukan di tiket python), serta memasukkan komentar @ 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)

Hasil:

Montreal
uber
12.89
Mere
Francoise
noel
889

(Catatan: Saya menggunakan Mac OS X 10.8.4 dan menggunakan Python 2.7.3)

aseagram
sumber
1
remove_accentsdimaksudkan untuk menghilangkan aksen dari string unicode. Seandainya dilewatkan byte-string, ia mencoba untuk mengubahnya menjadi string unicode dengan unicode(input_str). Ini menggunakan pengkodean default python, yaitu "ascii". Karena file Anda dikodekan dengan UTF-8, ini akan gagal. Baris 2 dan 3 mengubah encoding default python ke UTF-8, jadi itu berfungsi, seperti yang Anda tahu. Pilihan lain adalah untuk melewatkan remove_accentsstring unicode: hapus baris 2 dan 3, dan pada baris terakhir ganti elementdengan element.decode("utf-8"). Saya menguji: itu berhasil. Saya akan memperbarui jawaban saya untuk membuat ini lebih jelas.
MiniQuark
Suntingan bagus, poin bagus. (Pada catatan lain: Masalah sebenarnya yang saya sadari adalah file data saya tampaknya dikodekan iso-8859-1, sayangnya saya tidak bisa bekerja dengan fungsi ini!)
aseagram
aseagram: cukup ganti "utf-8" dengan "iso-8859-1", dan itu akan berfungsi. Jika Anda menggunakan windows, maka Anda sebaiknya menggunakan "cp1252".
MiniQuark
BTW, reload(sys); sys.setdefaultencoding("utf-8")adalah hack yang meragukan yang kadang-kadang direkomendasikan untuk sistem Windows; lihat stackoverflow.com/questions/28657010/… untuk detailnya.
PM 2Ring
14

gensim.utils.deaccent (teks) dari Gensim - pemodelan topik untuk manusia :

'Sef chomutovskych komunistu dostal postou bily prasek'

Solusi lain adalah unidecode .

Perhatikan bahwa solusi yang disarankan dengan unicodedata biasanya menghilangkan aksen hanya dalam beberapa karakter (misalnya berubah 'ł'menjadi '', bukan menjadi 'l').

Piotr Migdal
sumber
1
deaccentmasih memberi łbukannya l.
lcieslak
Anda tidak perlu menginstal NumPydan SciPymenghilangkan aksen.
Nuno André
terima kasih untuk referensi gensim! bagaimana cara membandingkannya dengan unidecode (dalam hal kecepatan atau akurasi)?
Etienne Kintzler
3

Beberapa bahasa telah menggabungkan diakritik sebagai huruf bahasa dan aksen diakritik untuk menentukan aksen.

Saya pikir lebih aman untuk menentukan secara eksplisit diactrics apa yang ingin Anda hapus:

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
sumber