Cách tốt nhất để loại bỏ các dấu trong chuỗi unicode Python là gì?


507

Tôi có một chuỗi Unicode bằng Python và tôi muốn xóa tất cả các dấu (dấu phụ).

Tôi tìm thấy trên Web một cách thanh lịch để làm điều này trong Java:

  1. chuyển đổi chuỗi Unicode thành dạng chuẩn hóa dài của nó (với một ký tự riêng cho các chữ cái và dấu phụ)
  2. xóa tất cả các ký tự có loại Unicode là "dấu phụ".

Tôi có cần phải cài đặt một thư viện như pyICU hay điều này có thể chỉ với thư viện chuẩn python? Còn trăn 3 thì sao?

Lưu ý quan trọng: Tôi muốn tránh mã có ánh xạ rõ ràng từ các ký tự có dấu sang đối tác không có dấu của chúng.

Câu trả lời:


448

Unidecode là câu trả lời chính xác cho điều này. Nó chuyển ngữ bất kỳ chuỗi unicode thành biểu diễn gần nhất có thể có trong văn bản ascii.

Thí dụ:

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
Có vẻ như làm việc tốt với người Trung Quốc, nhưng việc chuyển đổi tên tiếng Pháp "François" không may cho "FranASSois", điều này không tốt lắm, so với "Francois" tự nhiên hơn.
Eric O Lebigot

10
phụ thuộc vào những gì bạn đang cố gắng để đạt được. ví dụ tôi đang thực hiện tìm kiếm ngay bây giờ và tôi không muốn phiên âm tiếng Hy Lạp / tiếng Nga / tiếng Trung Quốc, tôi chỉ muốn thay thế "/ ę / ś / ć" bằng "a / e / s / c"
kolinko

58
@EOL unidecode hoạt động rất tốt cho các chuỗi như "François", nếu bạn truyền các đối tượng unicode cho nó. Có vẻ như bạn đã thử với một chuỗi byte đơn giản.
Karl Bartel

26
Lưu ý rằng unidecode> = 0,04.10 (tháng 12 năm 2012) là GPL. Sử dụng các phiên bản trước đó hoặc kiểm tra github.com/kmike/text-unidecode nếu bạn cần giấy phép dễ dãi hơn và có thể chịu một triển khai tồi tệ hơn một chút.
Mikhail Korobov

10
unidecodethay thế °bằng deg. Nó làm nhiều hơn là chỉ loại bỏ các dấu.
Eric Duminil

274

Còn cái này thì sao:

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

Điều này cũng hoạt động trên các chữ cái Hy Lạp:

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

Các loại nhân vật "Mn" là viết tắt của Nonspacing_Mark, mà là tương tự như unicodedata.combining trong câu trả lời của MiniQuark (Tôi không nghĩ unicodedata.combining, nhưng nó có lẽ là giải pháp tốt hơn, vì nó rõ ràng hơn).

Và hãy nhớ, những thao tác này có thể làm thay đổi đáng kể ý nghĩa của văn bản. Dấu, Umlauts, vv không phải là "trang trí".


6
Thật không may, đây không phải là những nhân vật được sáng tác - mặc dù "ł" được đặt tên là "LATIN SMALL LETTER L VỚI STROKE"! Bạn sẽ cần chơi các trò chơi với phân tích cú pháp unicodedata.name, hoặc chia nhỏ và sử dụng một bảng trông giống nhau - dù sao bạn cũng cần các chữ cái Hy Lạp (chỉ là "GREEK LITALTER LETTER ALPHA").
alexis

2
@andi, tôi sợ tôi không thể đoán được điểm bạn muốn làm. Trao đổi email phản ánh những gì tôi đã viết ở trên: Bởi vì chữ "ł" không phải là một chữ cái có dấu (và không được coi là một trong tiêu chuẩn Unicode), nên nó không có sự phân tách.
alexis

2
@alexis (theo dõi muộn): Điều này cũng hoạt động hoàn toàn tốt cho tiếng Hy Lạp - ví dụ. "GREEK VỐN THƯỞNG ALPHA VỚI DASIA VÀ VARIA" được chuẩn hóa thành "GREEK VỐN THƯỞNG ALPHA" đúng như mong đợi. Trừ khi bạn đang đề cập đến việc chuyển ngữ (ví dụ: "α" → "a"), không giống như "xóa dấu" ...
lenz

@lenz, tôi đã không nói về việc loại bỏ các dấu từ Hy Lạp, nhưng về "nét" trên hình elip. Vì nó không phải là dấu phụ, nên việc thay đổi nó thành hình elip đơn giản giống như thay đổi tiếng Hy Lạp Alpha thành A. Nếu không muốn thì đừng làm, nhưng trong cả hai trường hợp, bạn đều thay thế một kiểu Latin (gần) giống nhau.
alexis

Chủ yếu là hoạt động tốt :) Nhưng nó không chuyển ßthành ascii sstrong ví dụ. Tôi vẫn sẽ sử dụng unidecodeđể tránh tai nạn.
Nghệ thuật

146

Tôi chỉ tìm thấy câu trả lời này trên 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

Nó hoạt động tốt (ví dụ như tiếng Pháp), nhưng tôi nghĩ rằng bước thứ hai (loại bỏ dấu) có thể được xử lý tốt hơn so với việc bỏ các ký tự không phải ASCII, vì điều này sẽ thất bại đối với một số ngôn ngữ (ví dụ tiếng Hy Lạp). Giải pháp tốt nhất có lẽ là loại bỏ rõ ràng các ký tự unicode được gắn thẻ là dấu phụ.

Chỉnh sửa : đây là mẹo:

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)sẽ trả về true nếu ký tự ccó thể được kết hợp với ký tự trước, điều đó chủ yếu là nếu đó là dấu phụ.

Chỉnh sửa 2 : remove_accentsmong đợi một chuỗi unicode , không phải là một chuỗi byte. Nếu bạn có một chuỗi byte, thì bạn phải giải mã nó thành một chuỗi unicode như thế này:

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
Tôi đã phải thêm 'utf8' vào unicode:nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))
Jabba

@Jabba: , 'utf8'là "mạng an toàn" cần thiết nếu bạn đang kiểm tra đầu vào trong thiết bị đầu cuối (theo mặc định không sử dụng unicode). Nhưng thông thường, bạn không cần phải thêm nó, vì nếu bạn đang xóa dấu thì input_strrất có thể là utf8. Nó không đau để được an toàn, mặc dù.
MestreLion

1
@rbp: bạn nên chuyển một chuỗi unicode remove_accentsthay vì một chuỗi thông thường (u "é" thay vì "é"). Bạn đã chuyển một chuỗi thông thường sang remove_accents, vì vậy khi cố gắng chuyển đổi chuỗi của bạn thành chuỗi unicode, asciimã hóa mặc định đã được sử dụng. Mã hóa này không hỗ trợ bất kỳ byte nào có giá trị> 127. Khi bạn nhập "é" vào trình bao của mình, HĐH của bạn đã mã hóa mã đó, có thể bằng mã hóa UTF-8 hoặc một số Mã trang Windows và bao gồm các byte> 127. Tôi sẽ thay đổi chức năng của mình để xóa chuyển đổi thành unicode: nó sẽ đánh bom rõ ràng hơn nếu một chuỗi không unicode được thông qua.
MiniQuark

1
@MiniQuark hoạt động hoàn hảo >>> remove_accents (unicode ('é'))
rbp

1
Câu trả lời này đã cho tôi kết quả tốt nhất trên một tập dữ liệu lớn, ngoại lệ duy nhất là "ð" - unicodingata sẽ không chạm vào nó!
s29

43

Trên thực tế tôi làm việc trên python 2.6, 2.7 và 3.4 tương thích với dự án và tôi phải tạo ID từ các mục nhập của người dùng miễn phí.

Nhờ bạn, tôi đã tạo ra chức năng này hoạt động kỳ diệu.

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

kết quả:

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

2
Với Py2.7, chuyển một lỗi chuỗi unicode đã có tại text = unicode(text, 'utf-8'). Một cách giải quyết khác là thêmexcept TypeError: pass
Daniel Reis

Rất ồn ào! Làm việc trong trường hợp của tôi. Uma seleção de poesia brasileira para desenvolver a dungidade de escuta dos alunos idioma Português.
Aaron

23

Điều này không chỉ xử lý các dấu, mà còn "nét" (như trong ø, v.v.):

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

Đây là cách thanh lịch nhất mà tôi có thể nghĩ đến (và nó đã được alexis đề cập trong một bình luận trên trang này), mặc dù tôi không nghĩ rằng nó thực sự rất thanh lịch. Trên thực tế, đó là một vụ hack nhiều hơn, như đã chỉ ra trong các bình luận, vì tên Unicode là - thực sự chỉ là tên, chúng không đảm bảo tính nhất quán hay bất cứ điều gì.

Vẫn còn những chữ cái đặc biệt không được xử lý bởi điều này, chẳng hạn như chữ cái bị lật và đảo ngược, vì tên unicode của chúng không chứa 'VỚI'. Nó phụ thuộc vào những gì bạn muốn làm anyway. Đôi khi tôi cần tước giọng để đạt được thứ tự sắp xếp từ điển.

EDIT LƯU Ý:

Đề xuất kết hợp từ các nhận xét (xử lý lỗi tra cứu, mã Python-3).


8
Bạn nên bắt ngoại lệ nếu biểu tượng mới không tồn tại. Ví dụ: có SQUARE VỚI FILL CHỨNG NHẬN, nhưng không có SQUARE. (không đề cập đến việc mã này biến đổi UMBRELLA VỚI RAIN DROPS thành UMBRELLA).
janek37

Điều này có vẻ thanh lịch trong việc khai thác các mô tả ngữ nghĩa của các nhân vật có sẵn. Chúng ta có thực sự cần unicodechức năng gọi trong đó với python 3 không? Tôi nghĩ rằng một regex chặt chẽ hơn thay vì findsẽ tránh tất cả những rắc rối được đề cập trong bình luận ở trên, và đồng thời, việc ghi nhớ sẽ giúp thực hiện khi đó là một đường dẫn mã quan trọng.
matanster

1
@matanster không, đây là một câu trả lời cũ từ thời Python-2; các unicodethợ đúc chư không còn thích hợp trong Python 3. Trong mọi trường hợp, trong kinh nghiệm của tôi không có phổ biến, giải pháp thanh lịch cho vấn đề này. Tùy thuộc vào ứng dụng, bất kỳ phương pháp nào cũng có ưu và nhược điểm của nó. Các công cụ phát triển chất lượng như unidecodeđược dựa trên các bảng thủ công. Một số tài nguyên (bảng, thuật toán) được Unicode cung cấp, vd. cho đối chiếu.
lenz

1
Tôi chỉ nhắc lại, những gì ở trên (py3): 1) unicode (char) -> char 2) thử: return ud.lookup (desc) ngoại trừ KeyError: return char
mirek 8/11/19

@mirek bạn nói đúng: vì chủ đề này rất phổ biến, câu trả lời này xứng đáng được cập nhật / cải thiện. Tôi đã chỉnh sửa nó.
lenz

15

Đáp lại câu trả lời của @ MiniQuark:

Tôi đã cố đọc trong một tệp csv là một nửa tiếng Pháp (có dấu) và một số chuỗi cuối cùng sẽ trở thành số nguyên và số float. Để thử nghiệm, tôi đã tạo một test.txttệp trông như thế này:

Montréal, über, 12,89, Mère, Françoir, noël, 889

Tôi đã phải bao gồm các dòng 23để làm cho nó hoạt động (mà tôi tìm thấy trong một vé trăn), cũng như kết hợp nhận xét của @ 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)

Kết quả:

Montreal
uber
12.89
Mere
Francoise
noel
889

(Lưu ý: Tôi đang dùng Mac OS X 10.8.4 và sử dụng Python 2.7.3)


1
remove_accentscó nghĩa là để loại bỏ các dấu từ một chuỗi unicode. Trong trường hợp nó đã truyền một chuỗi byte, nó cố gắng chuyển đổi nó thành một chuỗi unicode với unicode(input_str). Cái này sử dụng mã hóa mặc định của python, đó là "ascii". Vì tệp của bạn được mã hóa bằng UTF-8, điều này sẽ thất bại. Dòng 2 và 3 thay đổi mã hóa mặc định của python thành UTF-8, do đó, nó hoạt động, như bạn đã tìm ra. Một tùy chọn khác là truyền remove_accentschuỗi unicode: xóa dòng 2 và 3 và trên dòng cuối cùng thay thế elementbằng element.decode("utf-8"). Tôi đã thử nghiệm: nó hoạt động. Tôi sẽ cập nhật câu trả lời của tôi để làm cho điều này rõ ràng hơn.
MiniQuark

Chỉnh sửa đẹp, điểm tốt. (Trên lưu ý khác: Vấn đề thực sự tôi đã nhận ra là các tập tin dữ liệu của tôi được rõ ràng mã hóa trong iso-8859-1, mà tôi không thể có được để làm việc với chức năng này, không may!)
aseagram

aseagram: chỉ cần thay thế "utf-8" bằng "iso-8859-1", và nó sẽ hoạt động. Nếu bạn đang ở trên windows, thì có lẽ bạn nên sử dụng "cp1252".
MiniQuark

BTW, reload(sys); sys.setdefaultencoding("utf-8")là một hack đáng ngờ đôi khi được khuyến nghị cho các hệ thống Windows; xem stackoverflow.com/questions/28657010/ trên để biết chi tiết.
PM 2Ring

14

gensim.utils.deaccent (văn bản) từ Gensim - mô hình chủ đề cho con người :

'Sef chomutovskych komunistu dostal postou bily prasek'

Một giải pháp khác là unidecode .

Lưu ý rằng giải pháp được đề xuất với unicodingata thường loại bỏ các dấu chỉ trong một số ký tự (ví dụ: nó biến 'ł'thành '', thay vì thành 'l').


1
deaccentvẫn cho łthay vì l.
lcieslak

Bạn không cần phải cài đặt NumPySciPyxóa dấu.
Nuno André

cảm ơn đã tham khảo gensim! Làm thế nào để nó so sánh với unidecode (về tốc độ hoặc độ chính xác)?
Etienne Kintzler

3

Một số ngôn ngữ đã kết hợp dấu phụ như chữ cái ngôn ngữ và dấu phụ dấu trọng âm để chỉ định dấu trọng âm.

Tôi nghĩ sẽ an toàn hơn khi chỉ định rõ ràng loại diactrics nào bạn muốn tước:

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))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.