在Python 2.7中打印UTF-8字符

6

以下是我如何打开、读取和输出文件的方法。该文件是一个UTF-8编码的文件,用于表示Unicode字符。我想要打印前10个UTF-8字符,但下面代码片段的输出却打印出了10个奇怪的无法识别的字符。不知道是否有人有任何想法如何正确地打印?谢谢。

   with open(name, 'r') as content_file:
        content = content_file.read()
        for i in range(10):
            print content[i]

每一个奇怪的字符长这样:

敬礼, 林


1
分享文本文件的内容 - Rahul Kumar
1
你的控制台或tty必须支持该字符 - 你可能需要更改终端设置。 - cdarke
@cdarke,谢谢并点赞。我的控制台可以正确打印内容,这应该证明它支持UTF-8字符集。问题只在我打印content[i]时发生。如果您有任何想法,将会非常棒。 - Lin Ma
@RahulKumar,感谢并点赞。不确定如何在此处发布原始的原始文件,我尝试过并且我的控制台可以正确地打印内容,这应该证明它支持UTF-8字符集。问题只会发生在我打印content[i]时。如果您有任何想法,那就太好了。 - Lin Ma
你可能会发现这篇文章有帮助:实用的Unicode,它是由Stack Overflow老手Ned Batchelder撰写的。 - PM 2Ring
2个回答

14
当Unicode代码点(字符)被编码为UTF-8时,一些代码点会转换为单个字节,但许多代码点则需要多个字节来编码。标准的7位ASCII范围内的字符将被编码为单个字节,但是更奇特的字符通常需要更多字节来编码。
因此,如果您将这些多字节UTF-8序列分解为单个字节,就会出现奇怪的字符。有时候这些字节会对应于普通可打印字符,但通常情况下不会,所以您会看到代替符号�。
这是一个使用版权符号©、注册商标符号®和商标符号™的简短演示,它们在UTF-8中分别被编码为2、2和3个字节。我的终端设置为使用UTF-8。
utfbytes = "\xc2\xa9 \xc2\xae \xe2\x84\xa2"
print utfbytes, len(utfbytes)
for b in utfbytes:
    print b, repr(b)

uni = utfbytes.decode('utf-8')
print uni, len(uni)

输出

© ® ™ 9'\xc2''\xa9'                                                                                                                                       
  ' ''\xc2''\xae'
  ' ''\xe2''\x84''\xa2'
© ® ™ 5

Stack Overflow的联合创始人Joel Spolsky写了一篇关于Unicode的好文章:绝对必要:每个软件开发者都必须知道的有关Unicode和字符集的最低限度(无任何借口!)

你还应该查看Python文档中的 Unicode HOWTO 文章,以及Ned Batchelder的 务实的Unicode 文章,也称为“Unipain”。


这是一个从UTF-8编码的字节串中提取单个字符的简短示例。正如我在评论中提到的那样,要正确地执行此操作,您需要知道每个字符编码为多少个字节。

utfbytes = "\xc2\xa9 \xc2\xae \xe2\x84\xa2"
widths = (2, 1, 2, 1, 3)
start = 0
for w in widths:
    print "%d %d [%s]" % (start, w, utfbytes[start:start+w])
    start += w

输出

0 2 [©]
2 1 [ ]
3 2 [®]
5 1 [ ]
6 3 [™]

顺带一提,这是那段代码的Python 3版本:

utfbytes = b"\xc2\xa9 \xc2\xae \xe2\x84\xa2"
widths = (2, 1, 2, 1, 3)
start = 0
for w in widths:
    s = utfbytes[start:start+w]
    print("%d %d [%s]" % (start, w, s.decode()))
    start += w

如果我们不知道UTF-8字符串中字符的字节宽度,那么我们需要做更多的工作。每个UTF-8序列在第一个字节中编码了序列的宽度,如维基百科关于UTF-8的文章所述。

以下Python 2演示展示了如何提取该宽度信息; 它产生与前两个代码片段相同的输出。

# UTF-8 code widths
#width starting byte
#1 0xxxxxxx
#2 110xxxxx
#3 1110xxxx
#4 11110xxx
#C 10xxxxxx

def get_width(b):
    if b <= '\x7f':
        return 1
    elif '\x80' <= b <= '\xbf':
        #Continuation byte
        raise ValueError('Bad alignment: %r is a continuation byte' % b)
    elif '\xc0' <= b <= '\xdf':
        return 2
    elif '\xe0' <= b <= '\xef':
        return 3
    elif '\xf0' <= b <= '\xf7':
        return 4
    else:
        raise ValueError('%r is not a single byte' % b)


utfbytes = b"\xc2\xa9 \xc2\xae \xe2\x84\xa2"
start = 0
while start < len(utfbytes):
    b = utfbytes[start]
    w = get_width(b)
    s = utfbytes[start:start+w]
    print "%d %d [%s]" % (start, w, s)
    start += w

通常情况下,不应该需要这样做:只需使用提供的解码方法即可。


对于好奇的人,这里有一个Python 3版本的get_width函数,以及一个手动解码UTF-8字节串的函数。

def get_width(b):
    if b <= 0x7f:
        return 1
    elif 0x80 <= b <= 0xbf:
        #Continuation byte
        raise ValueError('Bad alignment: %r is a continuation byte' % b)
    elif 0xc0 <= b <= 0xdf:
        return 2
    elif 0xe0 <= b <= 0xef:
        return 3
    elif 0xf0 <= b <= 0xf7:
        return 4
    else:
        raise ValueError('%r is not a single byte' % b)

def decode_utf8(utfbytes):
    start = 0
    uni = []
    while start < len(utfbytes):
        b = utfbytes[start]
        w = get_width(b)
        if w == 1:
            n = b
        else:
            n = b & (0x7f >> w)
            for b in utfbytes[start+1:start+w]:
                if not 0x80 <= b <= 0xbf:
                    raise ValueError('Not a continuation byte: %r' % b)
                n <<= 6
                n |= b & 0x3f
        uni.append(chr(n))
        start += w
    return ''.join(uni)


utfbytes = b'\xc2\xa9 \xc2\xae \xe2\x84\xa2'
print(utfbytes.decode('utf8'))
print(decode_utf8(utfbytes))

输出

© ® ™
© ® ™


感谢PM 2Ring,我为你的回复投票支持。尝试了你的方法,效果相当不错。我还有一个进一步的问题,如果原始字符串既包含Unicode字符(例如中文/日文字符),也在同一字符串中使用UTF-8编码的英文字符,假设第一个字符是中文字符,第二个字符是ASCII字母“a”(两者都是UTF-8编码)。在我调用utfbytes.decode(' utf-8')之后,当我通过utfbytes[1]引用第二个字符时,它能正确识别“a”吗? - Lin Ma
我有一个疑惑,因为你提到了多字节和单字节字符,我想知道如果在原始的UTF-8编码字符串中混合使用它们,它们会如何工作。谢谢。 - Lin Ma
顺便说一下,当我引用 utfbytes[1] 时,a 能够正确输出,只是为了确认我的理解是正确的。谢谢。 - Lin Ma
1
@LinMa:很高兴你解决了问题;我之前没有回复是因为你发表评论时我的时区已经是凌晨3点了。像utfbytes这样的字符串索引操作很棘手,除非你知道组件的字节大小。顺便说一下,Python 3中的Unicode处理通常比Python 2更好(也更少令人困惑)。 - PM 2Ring

5

要将Unicode字符串输出到文件或控制台,您需要选择文本编码。在Python中,默认的文本编码是ASCII,但为了支持其他字符,您需要使用不同的编码,例如UTF-8:

s = unicode(your_object).encode('utf8')
print s

谢谢 U.Swap,点个赞。我应该在你的代码中用 content 替换 your_object 吗? - Lin Ma

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