當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
堆棧溢出的聯合創始人,喬爾Spolsky的,已經寫在統一的好文章:The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
你也應該看看在Unicode HOWTO文章Python文檔和Ned Batchelder的Pragmatic 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 [™]
FWIW,這裏是一個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序列在第一個字節中編碼序列的寬度,如the Wikipedia article on 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))
輸出
©®™
©®™
份額的文本文件內容 –
控制檯或TTY必須支持的字符,以及 - 你可能需要更改終端設置。 – cdarke
@cdarke,感謝和投票了。我的控制檯可以正確地打印內容,這應該證明它支持UTF-8字符。這個問題只發生在我打印'content [i]'的時候。如果你有任何想法,那將會很棒。 –