簡體   English   中英

Python utf-8 編碼不遵循 unicode 規則

[英]Python utf-8 encoding not following unicode rules

背景:我有一個使用 unicode 編碼的字節文件。 但是,我想不出讓 Python 將其解碼為字符串的正確方法。 有時是使用 1 字節的 ASCII 文本。 大多數時候它使用 2 字節的“純拉丁文”文本,但它可能包含任何 unicode 字符。 所以我的 python 程序需要能夠解碼並處理它。 不幸的是byte_string.decode('unicode')不是問題,所以我需要指定另一種編碼方案。 使用 Python 3.9

我已經閱讀了 unicode 和 utf-8 Python doc上的 Python 文檔。 如果 Python 對它的字符串使用 unicode,默認使用 utf-8,這應該非常簡單,但我總是得到不正確的解碼。

如果我了解 unicode 的工作原理,最高有效字節是字符代碼,最低有效字節是解碼表中的查找值。 所以我希望 0x00_41 解碼為“A”,
0x00_F2 => 在此處輸入圖像描述
x65_03_01 => é(e 與重音組合)。

我寫了一個簡短的測試文件來試驗這些字節組合,但我遇到了一些我不明白的情況(盡管進行了大量閱讀)。

示例代碼:

def main():
    print("Starting MAIN...")

    vrsn_bytes = b'\x76\x72\x73\x6E'
    serato_bytes = b'\x00\x53\x00\x65\x00\x72\x00\x61\x00\x74\x00\x6F'
    special_bytes = b'\xB2\xF2'  
    combining_bytes = b'\x41\x75\x64\x65\x03\x01'  

    print(f"vrsn_bytes: {vrsn_bytes}")
    print(f"serato_bytes: {serato_bytes}")
    print(f"special_bytes: {special_bytes}")
    print(f"combining_bytes: {combining_bytes}")
    
    encoding_method = 'utf-8'  # also tried latin-1 and cp1252
    vrsn_str = vrsn_bytes.decode(encoding_method)
    serato_str = serato_bytes.decode(encoding_method)
    special_str = special_bytes.decode(encoding_method)
    combining_str = combining_bytes.decode(encoding_method)
    print(f"vrsn_str: {vrsn_str}")
    print(f"serato_str: {serato_str}")
    print(f"special_str: {special_str}")
    print(f"combining_str: {combining_str}")

    return True

if __name__ == '__main__':

    print("Starting Command Line Experiment!")
    
    if not main():
        print("\n Command Line Test FAILED!!")
    else:
        print("\n Command Line Test PASSED!!")

問題 1:utf-8 編碼。 在編寫實驗時,出現以下錯誤:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb2 in position 0: invalid start byte

我不明白為什么解碼失敗,根據unicode解碼表,0x00B2應該是“SUPERSCRIPT TWO”。 事實上,似乎 0x7F 以上的任何內容都會返回相同的 UnicodeDecodeError。

我知道有些編碼方案只支持 7 位,這似乎正在發生,但 utf-8 不僅應該支持 8 位,還應該支持多字節。

如果我將encoding_method更改為encoding_method = 'latin-1'將原始 ascii 128 個字符擴展到 256 個字符(最多 0xFF),那么我會得到更好的輸出:

vrsn_str: vrsn
serato_str: Serato
special_str: ²ò
combining_str: Aude

但是,這種編碼沒有正確處理 2 字節代碼。 \x00_53 應該是S ,而不是 �S ,我將在這篇文章中提到的編碼方法都沒有正確處理Aude之后的組合尖音符。

到目前為止,我已經嘗試了許多不同的編碼方法,但最接近的是:unicode_escape、latin-1 和 cp1252。 雖然我希望 utf-8 是我應該使用的,但它的行為並不像上面鏈接的 Python 文檔中描述的那樣。

任何幫助表示贊賞。 除了嘗試更多方法,我不明白為什么這不是根據鏈接 3 中的表進行解碼。

這實際上不是 python 問題,而是您對字符進行編碼的方式。 要將 unicode 代碼點轉換為 utf-8,您不能簡單地從代碼點位置獲取字節。

例如,代碼點 U+2192 是 →。 utf-8 中的實際二進制表示是:0xE28692,或 11100010 10000110 10010010

如我們所見,這是 3 個字節,而不是我們僅使用位置時所期望的 2 個字節。 要獲得正確的行為,您可以手動進行編碼,也可以使用如下轉換器:

https://onlineunicodetools.com/convert-unicode-to-binary

這將使您輸入一個 unicode 字符並獲得 utf-8 二進制表示。

要獲得 ò 的正確輸出,我們需要使用 0xC3B2。

>>> s = b'\xC3\xB2'
>>> print(s.decode('utf-8'))
ò

不能使用直接二進制表示的原因是字節的標頭。 在 utf-8 中,我們可以有 1 字節、2 字節和 4 字節代碼點。 例如,為了表示一個 1 字節的代碼點,第一位編碼為 0。這意味着我們只能存儲 2^7 個 1 字節的代碼點。 因此,控制字符代碼點 U+0080 必須編碼為 2 字節字符,例如 11000010 10000000。

對於這個字符,第一個字節以 header 110 開頭,而第二個字節以 header 10 開頭。這意味着代碼點的數據存儲在第一個字節的最后 5 位和第二個字節的最后 6 位字節。 如果我們將它們組合起來,我們會得到 00010 000000,相當於 0x80。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM