วิธีที่ดีที่สุดในการลบเครื่องหมายเน้นเสียงในสตริง Python unicode คืออะไร


507

ฉันมีสตริง Unicode ใน Python และฉันต้องการที่จะลบสำเนียงทั้งหมด (กำกับออกเสียง)

ฉันพบบนเว็บเป็นวิธีที่ยอดเยี่ยมในการทำเช่นนี้ใน Java:

  1. แปลงสตริง Unicode เป็นรูปแบบปกติที่มีความยาว (โดยมีอักขระแยกต่างหากสำหรับตัวอักษรและกำกับเสียง)
  2. ลบอักขระทั้งหมดที่มีชนิด Unicode เป็น "diacritic"

ฉันจำเป็นต้องติดตั้งไลบรารี่เช่น pyICU หรือเป็นไปได้หรือไม่กับไลบรารี่มาตรฐานของไพ ธ อน และไพ ธ อน 3 เป็นอย่างไร?

หมายเหตุสำคัญ: ฉันต้องการหลีกเลี่ยงรหัสที่มีการแมปที่ชัดเจนจากอักขระที่เน้นเสียงไปยังคู่ที่ไม่ได้เน้นเสียง

คำตอบ:


447

Unidecodeเป็นคำตอบที่ถูกต้องสำหรับสิ่งนี้ มันแปลสตริง unicode ใด ๆ ลงในการแสดงที่ใกล้เคียงที่สุดในข้อความ ASCII

ตัวอย่าง:

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
ดูเหมือนว่าจะทำงานได้ดีกับภาษาจีน แต่การเปลี่ยนชื่อภาษาฝรั่งเศส "François" น่าเสียดายที่ทำให้ "FranASSois" ซึ่งไม่ดีมากเมื่อเทียบกับ "Francois" ที่เป็นธรรมชาติมากกว่า
Eric O Lebigot

10
ขึ้นอยู่กับสิ่งที่คุณพยายามที่จะบรรลุ เช่นตอนนี้ฉันกำลังค้นหาและฉันไม่ต้องการทับศัพท์กรีก / รัสเซีย / จีนฉันต้องการแทนที่ "ą / ę / ś / ć" ด้วย "a / e / s / c"
kolinko

58
@EOL unidecode ใช้งานได้ดีกับสตริงเช่น "François" ถ้าคุณส่งวัตถุ Unicode ไปให้ ดูเหมือนว่าคุณจะลองด้วยสตริงไบต์ธรรมดา
Karl Bartel

26
โปรดทราบว่า unidecode> = 0.04.10 (ธันวาคม 2012) คือ GPL ใช้รุ่นก่อนหน้าหรือตรวจสอบgithub.com/kmike/text-unidecodeหากคุณต้องการสิทธิ์การใช้งานที่อนุญาตเพิ่มเติมและสามารถใช้งานได้แย่ลงเล็กน้อย
Mikhail Korobov

10
unidecodeแทนที่ด้วย° degมันไม่ได้เป็นเพียงการลบสำเนียง
Eric Duminil

273

เกี่ยวกับสิ่งนี้:

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

ใช้ได้กับตัวอักษรกรีกด้วย:

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

หมวดตัวอักษร "Mn" ย่อมาNonspacing_Markซึ่งมีลักษณะคล้ายกับ unicodedata.combining ในคำตอบของ MiniQuark (ผมไม่ได้คิดว่า unicodedata.combining แต่มันน่าจะเป็นทางออกที่ดีกว่าเพราะมันเป็นที่ชัดเจนมากขึ้น)

และโปรดจำไว้ว่าการดัดแปลงเหล่านี้อาจเปลี่ยนความหมายของข้อความได้อย่างมาก สำเนียง Umlauts ฯลฯ ไม่ใช่ "การตกแต่ง"


6
น่าเสียดายที่แม้ว่าตัวอักษร "ł" จะมีชื่อว่า "LATIN SMALL LETTER L with STROKE"! คุณจะต้องเล่นเกมด้วยการแยกวิเคราะห์unicodedata.nameหรือแยกย่อยและใช้ตารางที่มีลักษณะคล้ายกัน - ซึ่งคุณต้องการตัวอักษรกรีกอยู่แล้ว (Αเป็นเพียง "ตัวอักษรกรีกตัวพิมพ์ใหญ่")
alexis

2
@ และฉันกลัวว่าฉันไม่สามารถคาดเดาสิ่งที่คุณต้องการทำ การแลกเปลี่ยนทางอีเมลสะท้อนถึงสิ่งที่ฉันเขียนด้านบน: เนื่องจากตัวอักษร "ł" ไม่ใช่ตัวอักษรที่เน้นเสียง (และไม่ถือว่าเป็นหนึ่งในมาตรฐาน Unicode) จึงไม่มีการย่อยสลาย
alexis

2
@alexis (การติดตามผลล่าช้า): วิธีนี้ใช้ได้ผลดีกับภาษากรีกเช่นกัน - "GREEK Capital LETTER ALPHA พร้อม DASIA และ VARIA" ได้รับการปรับสู่มาตรฐานเป็น "GREEK Capital LETTER ALPHA" ตามที่คาดไว้ หากคุณไม่ได้หมายถึงการทับศัพท์ (เช่น "α" → "a") ซึ่งไม่เหมือนกับ "เอาสำเนียง" ออกไป ...
lenz

@lenz ฉันไม่ได้พูดถึงการลบสำเนียงจากภาษากรีก แต่เกี่ยวกับ "จังหวะ" บน ell เพราะมันไม่ได้เป็นเครื่องหมายการออกเสียง, เปลี่ยนไป ell Aธรรมดาเป็นเช่นเดียวกับการเปลี่ยนกรีกอัลฟา ถ้าไม่ต้องการมันไม่ได้ทำ แต่ในทั้งสองกรณีคุณจะแทนละติน (ใกล้) เหมือนกัน
alexis

ส่วนใหญ่ใช้งานได้ดี :) แต่มันไม่ได้แปลงßเป็น ascii ssในตัวอย่าง ฉันยังคงใช้unidecodeเพื่อหลีกเลี่ยงอุบัติเหตุ
ศิลปะ

146

ฉันเพิ่งพบคำตอบนี้บนเว็บ:

import unicodedata

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

มันใช้งานได้ดี (ตัวอย่างเช่นภาษาฝรั่งเศส) แต่ฉันคิดว่าขั้นตอนที่สอง (การลบเครื่องหมายเน้นเสียง) สามารถจัดการได้ดีกว่าการวางอักขระที่ไม่ใช่ ASCII เนื่องจากการทำเช่นนี้จะล้มเหลวสำหรับบางภาษา (กรีกเป็นต้น) ทางออกที่ดีที่สุดอาจจะเป็นการลบอักขระยูนิโค้ดที่ติดแท็กอย่างชัดเจน

แก้ไข : นี่เป็นการหลอกลวง:

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)จะกลับมาจริงถ้าตัวละครcสามารถรวมกับตัวละครก่อนหน้านั่นคือถ้ามันเป็นนักแสดง

แก้ไข 2 : remove_accentsคาดหวังสตริงunicodeไม่ใช่สตริงไบต์ หากคุณมีสตริงไบต์คุณต้องถอดรหัสสตริงเป็นยูนิโค้ดดังนี้:

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
ฉันต้องเพิ่ม 'utf8' เป็นยูนิโค้ด:nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))
Jabba

@ Jabba: , 'utf8'เป็น "เครือข่ายความปลอดภัย" ที่จำเป็นหากคุณกำลังทดสอบอินพุตในเทอร์มินัล (ซึ่งโดยค่าเริ่มต้นจะไม่ใช้ Unicode) แต่โดยปกติคุณไม่จำเป็นต้องเพิ่มเพราะถ้าคุณลบสำเนียงแล้วinput_strน่าจะ utf8 แล้ว แม้ว่ามันจะไม่เป็นอันตราย แต่อย่างใด
MestreLion

1
@rbp: คุณควรส่งสตริง Unicode ไปที่remove_accentsแทนที่จะเป็นสตริงปกติ (u "é" แทนที่จะเป็น "é") คุณส่งสตริงปกติไปremove_accentsดังนั้นเมื่อพยายามแปลงสตริงของคุณเป็นสตริง Unicode การasciiเข้ารหัสเริ่มต้นจะถูกใช้ การเข้ารหัสนี้ไม่สนับสนุนไบต์ใด ๆ ที่มีค่าเป็น> 127 เมื่อคุณพิมพ์ "é" ในเชลล์ระบบปฏิบัติการของคุณจะเข้ารหัสว่าอาจใช้รหัส UTF-8 หรือการเข้ารหัสหน้ารหัสของ Windows และมีไบต์> 127 ฉันจะเปลี่ยนฟังก์ชั่นของฉันเพื่อลบการแปลงเป็นยูนิโค้ด: มันจะระเบิดได้ชัดเจนยิ่งขึ้นถ้าผ่านสตริงที่ไม่ใช่ยูนิโค้ด
MiniQuark

1
@MiniQuark ที่ทำงานได้อย่างสมบูรณ์แบบ >>> remove_accents (unicode ('é'))
rbp

1
คำตอบนี้ให้ผลลัพธ์ที่ดีที่สุดกับชุดข้อมูลขนาดใหญ่ยกเว้นอย่างเดียวคือ "ð" - ยูนิโค้ดจะไม่แตะต้อง!
s29

43

จริงๆแล้วฉันทำงานกับงูหลามที่ใช้งานร่วมกันได้ของโครงการ 2.6, 2.7 และ 3.4 และฉันต้องสร้าง ID จากรายการผู้ใช้ฟรี

ขอบคุณคุณฉันได้สร้างฟังก์ชั่นนี้ที่ใช้งานได้มหัศจรรย์

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

ผลลัพธ์:

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, การส่งผ่านข้อผิดพลาดสตริง Unicode text = unicode(text, 'utf-8')แล้วที่ วิธีแก้ปัญหาสำหรับสิ่งนั้นคือการเพิ่มexcept TypeError: pass
Daniel Reis

เสียงดังมาก! ทำงานในกรณีของฉัน Uma seleção de poesia brasileira para desenvolver และความสามารถในการหลบหนีจากความวุ่นวาย
Aaron

23

การจัดการนี้ไม่เพียงเน้นสำเนียงเท่านั้น แต่ยังรวมถึง "สโตรก" (ดังในøเป็นต้น):

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

นี่เป็นวิธีที่ยอดเยี่ยมที่สุดที่ฉันสามารถนึกได้ (และได้รับการกล่าวถึงโดยอเล็กซิสในความคิดเห็นในหน้านี้) แม้ว่าฉันจะไม่คิดว่ามันจะสง่างามอย่างแน่นอน ในความเป็นจริงมันเป็นเรื่องของการแฮ็คมากกว่าที่ระบุไว้ในความคิดเห็นเนื่องจากชื่อ Unicode คือ - ชื่อจริง ๆ พวกเขาไม่รับประกันว่าจะสอดคล้องหรืออะไรก็ตาม

ยังมีตัวอักษรพิเศษที่ไม่ได้รับการจัดการโดยสิ่งนี้เช่นตัวอักษรแบบเปิดและแบบกลับด้านเนื่องจากชื่อยูนิโค้ดไม่มีชื่อ 'WITH' ขึ้นอยู่กับสิ่งที่คุณต้องการจะทำต่อไป บางครั้งฉันต้องการการเน้นสำเนียงเพื่อให้ได้ลำดับการจัดเรียงพจนานุกรม

แก้ไขหมายเหตุ:

คำแนะนำรวมจากความคิดเห็น (การจัดการข้อผิดพลาดการค้นหารหัส Python-3)


8
คุณควรตรวจสอบข้อยกเว้นหากไม่มีสัญลักษณ์ใหม่ ตัวอย่างเช่นมี SQUARE พร้อม VERTICAL FILL ▥ แต่ไม่มี SQUARE (ไม่ต้องพูดถึงว่ารหัสนี้แปลง UMBRELLA พร้อม RAIN DROPS ☔เป็น UMBRELLA ☂)
janek37

นี่ดูหรูหราในการควบคุมคำอธิบายความหมายของตัวละครที่มีอยู่ เราจำเป็นต้องมีunicodeการเรียกใช้ฟังก์ชันใน python 3 ด้วยหรือไม่? ฉันคิดว่า regex ที่เข้มงวดยิ่งขึ้นแทนการfindหลีกเลี่ยงปัญหาทั้งหมดที่กล่าวถึงในความคิดเห็นข้างต้นและการบันทึกช่วยจำจะช่วยให้ประสิทธิภาพการทำงานเมื่อเป็นเส้นทางรหัสที่สำคัญ
matanster

1
@matanster no นี่เป็นคำตอบเก่าจากยุค Python-2 unicodetypecast จะไม่เหมาะสมในหลาม 3. ในกรณีใด ๆ ในประสบการณ์ของผมไม่มีสากลทางออกที่สวยงามเพื่อแก้ไขปัญหานี้ วิธีการใดก็ตามมีข้อดีข้อเสีย เครื่องมือที่unidecodeมีคุณภาพที่เจริญรุ่งเรืองเช่นนี้สร้างขึ้นจากตารางที่สร้างขึ้นด้วยมือ ทรัพยากรบางอย่าง (ตารางอัลกอริทึม) มีให้โดย Unicode เช่น สำหรับการเปรียบเทียบ
lenz

1
ฉันเพิ่งทำซ้ำสิ่งที่อยู่เหนือ (py3): 1) unicode (char) -> ถ่าน 2) ลอง: return ud.lookup (เรียง) ยกเว้น KeyError: กลับถ่าน
mirek

@mirek คุณพูดถูก: เนื่องจากกระทู้นี้เป็นที่นิยมมากคำตอบนี้สมควรได้รับการปรับปรุง / ปรับปรุงบางอย่าง ฉันแก้ไขมัน
lenz

15

ในการตอบกลับของ @ MiniQuark คำตอบ:

ฉันพยายามอ่านในไฟล์ csv ที่เป็นแบบครึ่งภาษาฝรั่งเศส (มีเครื่องหมายเน้นเสียง) และยังมีบางสตริงที่ในที่สุดจะกลายเป็นจำนวนเต็มและลอย จากการทดสอบฉันสร้างtest.txtไฟล์ที่มีลักษณะดังนี้:

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

ฉันต้องรวมบรรทัด2และ3เพื่อให้มันทำงาน (ซึ่งฉันพบในตั๋วหลาม) รวมทั้งรวมความคิดเห็นของ @ 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)

ผลลัพธ์:

Montreal
uber
12.89
Mere
Francoise
noel
889

(หมายเหตุ: ฉันใช้ Mac OS X 10.8.4 และใช้ Python 2.7.3)


1
remove_accentsมีวัตถุประสงค์เพื่อลบสำเนียงจากสตริง Unicode ในกรณีที่มันผ่านไบต์สตริงก็พยายามที่จะแปลงเป็นสายอักขระ Unicode unicode(input_str)ด้วย วิธีนี้ใช้การเข้ารหัสเริ่มต้นของ python ซึ่งก็คือ "ascii" เนื่องจากไฟล์ของคุณถูกเข้ารหัสด้วย UTF-8 สิ่งนี้จะล้มเหลว บรรทัดที่ 2 และ 3 เปลี่ยนการเข้ารหัสเริ่มต้นของ python เป็น UTF-8 ดังนั้นจึงใช้งานได้ตามที่คุณค้นพบ อีกตัวเลือกหนึ่งคือการผ่านremove_accentsสตริง Unicode: สายลบที่ 2 และ 3 และในบรรทัดสุดท้ายแทนที่โดยelement element.decode("utf-8")ฉันทดสอบ: มันใช้งานได้ ฉันจะอัปเดตคำตอบเพื่อให้ชัดเจนยิ่งขึ้น
MiniQuark

การแก้ไขที่ดีจุดที่ดี (ในหมายเหตุอื่น: ปัญหาจริงที่ฉันรู้คือไฟล์ข้อมูลของฉันถูกเข้ารหัสอย่างชัดเจนiso-8859-1ซึ่งฉันไม่สามารถทำงานกับฟังก์ชั่นนี้โชคไม่ดี!)
aseagram

aseagram: เพียงแทนที่ "utf-8" ด้วย "iso-8859-1" และมันควรจะทำงาน หากคุณใช้ Windows คุณควรใช้ "cp1252" แทน
MiniQuark

BTW reload(sys); sys.setdefaultencoding("utf-8")เป็นแฮ็คที่น่าสงสัยซึ่งบางครั้งก็แนะนำสำหรับระบบ Windows ดูstackoverflow.com/questions/28657010/…สำหรับรายละเอียด
PM 2Ring

14

gensim.utils.deaccent (ข้อความ)จากGensim - การสร้างแบบจำลองหัวข้อสำหรับมนุษย์ :

'Sef chomutovskych komunistu dostal postou bily prasek'

ทางออกก็คือunidecode

โปรดทราบว่าโซลูชันที่แนะนำด้วยunicodedataมักจะลบเครื่องหมายเน้นเสียงในอักขระบางตัวเท่านั้น (เช่นเปลี่ยน'ł'เป็น''แทนที่จะเป็น'l')


1
deaccentยังคงให้แทนł l
lcieslak

คุณไม่จำเป็นต้องติดตั้งNumPyและSciPyลบสำเนียง
Nuno André

ขอบคุณสำหรับการอ้างอิง gensim! มันเปรียบเทียบกับ unidecode ได้อย่างไร (ในแง่ของความเร็วหรือความแม่นยำ)
Etienne Kintzler

3

บางภาษามีการรวมกำกับการออกเสียงเป็นตัวอักษรภาษาและการกำกับสำเนียงเพื่อระบุสำเนียง

ฉันคิดว่ามันปลอดภัยกว่าที่จะระบุอย่างชัดเจนว่าคุณต้องการตัดการแสดงโฆษณาใด:

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))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.