在Python中使用string.translate进行西里尔字母转换?

23

我在使用Python中的string.maketrans时遇到了UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-51: ordinal not in range(128)异常。我对以下代码(gist)中出现这种错误感到有些泄气:

# -*- coding: utf-8 -*-

import string

def translit1(string):
    """ This function works just fine """
    capital_letters = {
        u'А': u'A',
        u'Б': u'B',
        u'В': u'V',
        u'Г': u'G',
        u'Д': u'D',
        u'Е': u'E',
        u'Ё': u'E',
        u'Ж': u'Zh',
        u'З': u'Z',
        u'И': u'I',
        u'Й': u'Y',
        u'К': u'K',
        u'Л': u'L',
        u'М': u'M',
        u'Н': u'N',
        u'О': u'O',
        u'П': u'P',
        u'Р': u'R',
        u'С': u'S',
        u'Т': u'T',
        u'У': u'U',
        u'Ф': u'F',
        u'Х': u'H',
        u'Ц': u'Ts',
        u'Ч': u'Ch',
        u'Ш': u'Sh',
        u'Щ': u'Sch',
        u'Ъ': u'',
        u'Ы': u'Y',
        u'Ь': u'',
        u'Э': u'E',
        u'Ю': u'Yu',
        u'Я': u'Ya'
    }

    lower_case_letters = {
        u'а': u'a',
        u'б': u'b',
        u'в': u'v',
        u'г': u'g',
        u'д': u'd',
        u'е': u'e',
        u'ё': u'e',
        u'ж': u'zh',
        u'з': u'z',
        u'и': u'i',
        u'й': u'y',
        u'к': u'k',
        u'л': u'l',
        u'м': u'm',
        u'н': u'n',
        u'о': u'o',
        u'п': u'p',
        u'р': u'r',
        u'с': u's',
        u'т': u't',
        u'у': u'u',
        u'ф': u'f',
        u'х': u'h',
        u'ц': u'ts',
        u'ч': u'ch',
        u'ш': u'sh',
        u'щ': u'sch',
        u'ъ': u'',
        u'ы': u'y',
        u'ь': u'',
        u'э': u'e',
        u'ю': u'yu',
        u'я': u'ya'
    }

    translit_string = ""

    for index, char in enumerate(string):
        if char in lower_case_letters.keys():
            char = lower_case_letters[char]
        elif char in capital_letters.keys():
            char = capital_letters[char]
            if len(string) > index+1:
                if string[index+1] not in lower_case_letters.keys():
                    char = char.upper()
            else:
                char = char.upper()
        translit_string += char

    return translit_string


def translit2(text):
    """ This method should be more easy to grasp, 
    but throws exception:
    UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-51: ordinal not in range(128)
    """

    symbols = string.maketrans(u"абвгдеёзийклмнопрстуфхъыьэАБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЬЭ",
                               u"abvgdeezijklmnoprstufh'y'eABVGDEEZIJKLMNOPRSTUFH'Y'E")
    sequence = {
        u'ж':'zh',
        u'ц':'ts',
        u'ч':'ch',
        u'ш':'sh',
        u'щ':'sch',
        u'ю':'ju',
        u'я':'ja',
        u'Ж':'Zh',
        u'Ц':'Ts',
        u'Ч':'Ch'
    }

    for char in sequence.keys():
        text = text.replace(char, sequence[char])

    return text.translate(symbols)

if __name__ == "__main__":
    print translit1(u"Привет") # prints Privet as expected
    print translit2(u"Привет") # throws exception: UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-51: ordinal not in range(128)

原始跟踪:

Traceback (most recent call last):
  File "translit_error.py", line 124, in <module>
    print translit2(u"Привет") # throws exception: UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-51: ordinal not in range(128)
  File "translit_error.py", line 103, in translit2
    u"abvgdeezijklmnoprstufh'y'eABVGDEEZIJKLMNOPRSTUFH'Y'E")
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-51: ordinal not in range(128)

我的意思是,为什么Python中的string.maketrans要使用ascii表呢?而且英文字母怎么会超出0-128范围呢?

$ python -c "print ord(u'A')"
65
$ python -c "print ord(u'z')"
122
$ python -c "print ord(u\"'\")"
39

经过几个小时的努力,我感觉非常疲惫,无法解决这个问题。

有人能说出发生了什么以及如何修复吗?


1
你使用的是哪个版本的Python?我记得,Python 2 的 maketrans 无法处理非ASCII字符。(但Python 3应该没问题。) - kojiro
1
据我所记,maketrans 的 Unicode 版本要求您将 Unicode 字符映射到序数(我不知道为什么)。 - Blender
Python 2.7.3 - 抱歉,我没有指定。很遗憾它不在string.makestrans文档中。 - Nemoden
1
看一下 unidecode 模块,它的音译效果很好。 - Blender
谢谢大家,你们节省了我很多时间。@Blender,不幸的是unidecode对我来说不是一个选项(在@thg345的回答中解释了原因),尽管它很方便使用。@kojiro,上面的代码在Python 3中也可以正常工作,就像你说的那样。 - Nemoden
4个回答

33
你可以使用transliterate包(https://pypi.python.org/pypi/transliterate)。
示例#1:
from transliterate import translit
print translit("Lorem ipsum dolor sit amet", "ru")
# Лорем ипсум долор сит амет

例子 #2:

print translit(u"Лорем ипсум долор сит амет", "ru", reversed=True)
# Lorem ipsum dolor sit amet

Transliterate 1.7.3在希腊语方面存在一些问题 https://github.com/barseghyanartur/transliterate/issues/8 - Wtower

27

translate在使用unicode字符串时会有不同的表现。你需要提供一个字典ord(search)->ord(replace),而不是像使用普通字符串时一样提供一个maketrans表:

symbols = (u"абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ",
           u"abvgdeejzijklmnoprstufhzcss_y_euaABVGDEEJZIJKLMNOPRSTUFHZCSS_Y_EUA")

tr = {ord(a):ord(b) for a, b in zip(*symbols)}

# for Python 2.*:
# tr = dict( [ (ord(a), ord(b)) for (a, b) in zip(*symbols) ] )

text = u'Добрый Ден'
print text.translate(tr)  # looks good

话虽如此,我同意建议不要重复发明轮子,而是使用已经建立的库:http://pypi.python.org/pypi/Unidecode


1
谢谢,这个很好用。但我仍然很生气,因为文档中没有指向string.maketrans :) 不幸的是,当涉及到俄语中某些符号的转换时,unidecode处理西里尔字母的转写非常丑陋。我的目标是从用俄语编写的标题中制作URL短语(猜猜是谁?当然是Google),因此我需要制作一个Google可以“理解”的短语的转写。我尝试了一下俄语中的一个单词并将其输入到Google中,结果不太满意——Google说“也许你的意思是<另一个单词>”。 - Nemoden
1
有些字母被省略了,所以 C-V、C-P 方法会导致错误 :) 完整的俄语字母表版本应该是这样的: symbols = (u"абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", u"abvgdeejzijklmnoprstufhzcss_y_euaABVGDEEJZIJKLMNOPRSTUFHZCSS_Y_EUA") - Ben Usman
@MInner:这个翻译不太行,因为它只能进行一对一的替换。 - georg
@thg435 这是一对一的映射。俄语字母看起来似乎更宽:)这并不是批评,而是我花了几分钟时间思考为什么字符串中每个含有字母“ч”的东西都会失败的原因。 - Ben Usman
@MInner:好的,看起来我误解了你的评论。您介意编辑此信息以使其他遇到相同问题的人可以立即复制粘贴解决方案吗? - georg

15

请查看CyrTranslit软件包,它专门用于将西里尔字母文本转换为其他语言或将其他语言文本转换为西里尔字母文本。它目前支持塞尔维亚语、黑山语、马其顿语和俄语。

使用示例:

>>> import cyrtranslit
>>> cyrtranslit.supported()
['me', 'sr', 'mk', 'ru']

>>> cyrtranslit.to_latin('Моё судно на воздушной подушке полно угрей', 'ru')
'Moyo sudno na vozdushnoj podushke polno ugrej'

>>> cyrtranslit.to_cyrillic('Moyo sudno na vozdushnoj podushke polno ugrej')
'Моё судно на воздушной подушке полно угрей'

谢谢,我觉得这非常有用。 - concrete_rose

4

这里是另一种更准确的音译的简短解决方案:

symbols = (u"абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ ",
    (*list(u'abvgdee'), 'zh', *list(u'zijklmnoprstuf'), 'kh', 'z', 'ch', 'sh', 'sh', '',
    'y', '', 'e', 'yu','ya', *list(u'ABVGDEE'), 'ZH', 
    *list(u'ZIJKLMNOPRSTUF'), 'KH', 'Z', 'CH', 'SH', 'SH', *list(u'_Y_E'), 'YU', 'YA', ' '))

coding_dict = {source: dest for source, dest in zip(*symbols)}
translate = lambda x: ''.join([coding_dict[i] for i in x])

text = u'Добро пожаловать'
translate(text)
# 'Dobro pozhalovat'

如果您的名称中包含除西里尔字母以外的任何其他字符,则仍然存在问题。 - XCanG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接