在Python中将ASCII字符转换为Unicode全角拉丁字母?

14

您是否可以轻松地在 ASCII 字符和它们的亚洲全角 Unicode 全宽字符之间进行转换? 比如:

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!゛#$%&()*+、ー。/:;〈==〉?@[\\]^_‘{|}~

2
这可能与此有关:https://dev59.com/CnE95IYBdhLWcg3wKq6q - deceze
5
@Mark,除非你了解它,否则不要称某物为愚蠢之物。在亚洲写作中,这种方法被用来将拉丁文融入“双倍宽”的亚洲字符中,因为这样读起来更自然,尤其是在竖向文本和网格式块中。例如:これはPythonの質問だぞ。 - deceze
2
@deceze,感谢您的解释,也许我在称其为愚蠢时有些草率了。不过似乎应该有更好的方法来处理这个问题。 - Mark Ransom
7个回答

10

只需简单地使用chr(0xFF20 + ord(asciichar)) :) - werewindle
你确定吗?ValueError: chr()参数不在范围内(256) - user975135
糟糕。你使用的是Python2吗?那么 unichr(0xFF20 + ord(asciichar)) - werewindle
似乎还有些问题,但可能是API的特定问题。我能直接获取字符串对象数据而不是Unicode吗?然后从那里开始处理? :) - user975135
2
哦,抱歉。我在计算偏移量时出错了。我们应该使用 unichr(0xFEE0 + ord(asciichar))。现在它可以正常工作了。我已经修复了答案。 - werewindle
显示剩余6条评论

9

全角ASCII替代字符的范围从U+FF01开始,而不是U+FF00。奇怪的是,U+FF00没有定义。要获得全角空格,您需要使用U+3000 IDEOGRAPHIC SPACE。不要仅仅依靠键入看似所需内容并通过字符的视觉检查来检查映射 - unicodedata.name是您的朋友。以下是示例代码:

# coding: utf-8
from unicodedata import name as ucname

# OP
normal = u"""0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~"""
wide = u"""0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!゛#$%&()*+、ー。/:;〈=〉?@[\\]^_‘{|}~"""
# above after editing (had = twice)
widemapOP = dict((ord(x[0]), x[1]) for x in zip(normal, wide))

# Ingacio V
normal = u' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~'
wide = u' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!゛#$%&()*+、ー。/:;〈=〉?@[\\]^_‘{|}~'
widemapIV = dict((ord(x[0]), x[1]) for x in zip(normal, wide))

# JM
widemapJM = dict((i, i + 0xFF00 - 0x20) for i in xrange(0x21, 0x7F))
widemapJM[0x20] = 0x3000 # IDEOGRAPHIC SPACE

maps = {'OP': widemapOP, 'IV': widemapIV, 'JM': widemapJM}.items()

for i in xrange(0x20, 0x7F):
    a = unichr(i)
    na = ucname(a, '?')
    for tag, widemap in maps:
        w = a.translate(widemap)
        nw = ucname(w, '?')
        if nw != "FULLWIDTH " + na:
            print "%s: %04X %s => %04X %s" % (tag, i, na, ord(w), nw)

运行时会展示你真正拥有的东西:一些缺失的映射和一些特殊的映射:

JM: 0020 SPACE => 3000 IDEOGRAPHIC SPACE
IV: 0020 SPACE => 3000 IDEOGRAPHIC SPACE
OP: 0020 SPACE => 0020 SPACE
IV: 0022 QUOTATION MARK => 309B KATAKANA-HIRAGANA VOICED SOUND MARK
OP: 0022 QUOTATION MARK => 309B KATAKANA-HIRAGANA VOICED SOUND MARK
IV: 0027 APOSTROPHE => 0027 APOSTROPHE
OP: 0027 APOSTROPHE => 0027 APOSTROPHE
IV: 002C COMMA => 3001 IDEOGRAPHIC COMMA
OP: 002C COMMA => 3001 IDEOGRAPHIC COMMA
IV: 002D HYPHEN-MINUS => 30FC KATAKANA-HIRAGANA PROLONGED SOUND MARK
OP: 002D HYPHEN-MINUS => 30FC KATAKANA-HIRAGANA PROLONGED SOUND MARK
IV: 002E FULL STOP => 3002 IDEOGRAPHIC FULL STOP
OP: 002E FULL STOP => 3002 IDEOGRAPHIC FULL STOP
IV: 003C LESS-THAN SIGN => 3008 LEFT ANGLE BRACKET
OP: 003C LESS-THAN SIGN => 3008 LEFT ANGLE BRACKET
IV: 003E GREATER-THAN SIGN => 3009 RIGHT ANGLE BRACKET
OP: 003E GREATER-THAN SIGN => 3009 RIGHT ANGLE BRACKET
IV: 005C REVERSE SOLIDUS => 005C REVERSE SOLIDUS
OP: 005C REVERSE SOLIDUS => 005C REVERSE SOLIDUS
IV: 0060 GRAVE ACCENT => 2018 LEFT SINGLE QUOTATION MARK
OP: 0060 GRAVE ACCENT => 2018 LEFT SINGLE QUOTATION MARK

3
是的。
>>> normal = u' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~'
>>> wide = u' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!゛#$%&()*+、ー。/:;〈=〉?@[\\]^_‘{|}~'
>>> widemap = dict((ord(x[0]), x[1]) for x in zip(normal, wide))
>>> print u'Hello, world!'.translate(widemap)
Hello、 world!

+1 聪明!但我一直得到 Gello+  orldZ 作为“Hello world!”的翻译。 - juliomalegria
那是一个选项,嘿嘿。如果我不确定我的两个字符串是否正确或完整呢? - user975135
通过压缩字符串,将字符并排打印,并确保它们匹配。 - Ignacio Vazquez-Abrams
宽字符集是静态的,至少在Unicode联盟决定添加更多字符之前是这样的。 - Ignacio Vazquez-Abrams
3
解决方案需要费力的打字,因此容易出现问题,正如上面的评论所表明的那样。 - John Machin
显示剩余2条评论

3

是的,在Python 3中,最干净的方法是使用 str.translatestr.maketrans

HALFWIDTH_TO_FULLWIDTH = str.maketrans(
    '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&()*+,-./:;<=>?@[]^_`{|}~',
    '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!゛#$%&()*+、ー。/:;〈=〉?@[]^_‘{|}~')

def halfwidth_to_fullwidth(s):
    return s.translate(HALFWIDTH_TO_FULLWIDTH)

在Python 2中,str.maketrans已经被string.maketrans所替代,并且无法处理Unicode字符,因此您需要像Ignacio Vazquez在上面提到的那样制作一个字典。

1
这个答案对我来说比其他答案更好。它是一个更简洁、更短的解决方案,而且完全按照预期工作。虽然我猜想在2011年时还没有这个选项。 - confetti
你可能想要在列表中添加空格(' ')和全角空格(' ')以确保完整性。 - Stef
公平的观点 @Stef - 我只是回答了原问题,这个列表是不完整的。请参考Heizi的答案,有一个自动化的方式和完整的列表。 - Nils von Barth

2
我来这里是为了寻找一种将任何全角、半角或表意Unicode字符转换为其“正常”等效字符的方法,如果它们存在的话。最终我自己编写了解决方案,因为我想要一个不依赖于手动输入翻译字符串的解决方案,这种方法只会导致缺少/不正确的映射,正如John Machin答案所示。如果有用的话,以下是代码:
import unicodedata 
unicode_range = (0, 0x10ffff)

# create a dict of where the values are unicode characters
# and the keys are the character names, if they have one.
chars = {}
for uc_point in range(unicode_range[0], unicode_range[1]+1):
    char = chr(uc_point)
    try:
        name = unicodedata.name(char)
        chars[name] = char
    except ValueError: #chars with no name such as control characters
        pass

def normal(name):
    # 'IDEOGRAPHIC COMMA' -> 'COMMA'
    # 'HALFWIDTH IDEOGRAPHIC COMMA' -> 'COMMA'
    # 'LATIN SMALL LETTER A' -> None 
    # so we want to look foor these at the start of character names:
    starts = ['HALFWIDTH IDEOGRAPHIC','IDEOGRAPHIC','FULLWIDTH','HALFWIDTH']
    l = [name[len(start)+1:] for start in starts if name.startswith(start)]
    if l:
        return l[0]
    else:
        return None

# who doesn't love a bit of dict comprehension for the finish:
mapping = {chars[name]: chars[normal(name)] for name in chars if normal(name) in chars}

这样我们就得到了一个整洁的映射表,可以与str.maketrans()和str.translate()一起使用,如Nils von Barth的回答所示:
>>> ''.join(mapping.keys())
'\u3000、。!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⦅⦆。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワンᅠᄀᄁᆪᄂᆬᆭᄃᄄᄅᆰᆱᆲᆳᆴᆵᄚᄆᄇᄈᄡᄉᄊᄋᄌᄍᄎᄏᄐᄑ하ᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯᅰᅱᅲᅳᅴᅵ¢£¬ ̄¦¥₩←↑→↓■○'

"and"(并且)
>>> ''.join(mapping.values())
' ,.!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⦅⦆.「」,・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワンㅤㄱㄲㄳㄴㄵㄶㄷㄸㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅃㅄㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ¢£¬¯¦¥₩←↑→↓■○'

这个解决方案也是未来可靠的,因为它依赖于stdlib模块unicodedata,该模块经常更新最新的Unicode数据库。

0

这只走一条路:

#!/usr/bin/env perl
# uniwide
use utf8;
use strict;
use warnings;
use open qw(:std :utf8);

while (<>) {    
    s/\s/\x{A0}\x{A0}/g if tr
      <!"#$%&´()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~¢£>
      <!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~¢£>;;    
} continue {
      print;   
} 

close(STDOUT) || die "can't close stdout: $!";

这个也是一样的:

#!/usr/bin/env perl
# uninarrow
use utf8;
use strict;
use warnings;
use open qw(:std :utf8);

while (<>) {     
    s/  / /g if tr
      <!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~¢£>
      <!"#$%&´()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~¢£>    
} continue {
      print;    
} 

close(STDOUT) || die "can't close stdout: $!";

这是什么编程语言? - juliomalegria
1
由于某种原因,这个问题上的 python 标签真的太小了... :o) - deceze

-3

ASCII的UTF-8 Unicode代码完全相同。对于UTF-16,在(LE / BE)之前/之后添加零即可。

或者在Python中使用mystr.encode(“utf-8”)


3
我对此不太确定。我认为在 Unicode 中,你有普通的“宽度”ASCII字符,确实是相同的,但也有更宽的版本(如上文),它们有另一个代码。 - user975135
0123456789a != 0123456789a 的意思是不相等。 - glglgl
@glglgl 这个问题的提问方式不正确,我不得不编辑一下以清理它。 - sorin

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