Python原始檔一行字元過長造成的問題

Dxtr發表於2018-11-24

記錄一下 Python 原始檔一行字元過長從而造成的問題.
首先宣告下, 這個問題不是我遇到的, 是在刷華蟒的時候看到的一個帖子, 帖子地址在這兒, 有興趣的可以看看
起因是帖主在原始檔中定義了一個超長的集合, 該集合如下

e = {'合作社', '經濟', '財稅', '賬', '旅行社', '維修', '押運', '管理諮詢', '基業', '養生', '雕塑', '上海警', '首飾', '商行', '電腦', '人才網', '素食', '日用製品', '鋁製品', '百貨', '銀行', '產業園', '商學院', '駕駛培訓', '生活', '潛水服務', '電視臺', '貿易', '副食', '鋁業', '風投', '財經', '警察協會', '試驗', '展會', '金融', '陶藝', '營業部', '眼鏡', '物業', '飛手', '實驗', '幼教', '超市', '植保機使用者', '農服', '教育', '會展', '銷售', '果業', '保險', '生態園', '企業管理', '印刷', '電力局', '傳播', '貸', '拆遷', '裝飾', '養老', '人壽', '生活館', '財務', '餐飲', '諮詢服務', '出品', '個體', '辦公', '自由職業', '動漫', '中央', '商務', '老年人', '農資', '新華社', '博覽會', '新華網', '珠寶', '航展', '工貿', '專利', '市政', '體驗', '網咖', '水敏紙檢測噴潵效果', '破產', '大申網', '促進會', '酒業', '菸草', '家政', '威海警', '自來水', '代理', '售貨', '創意', '基金', '進出口', '融資', '銀聯', '製衣', '暫無', '成人', '影視', 'VC', '英烈', '風水', '營銷', '攝影', '小公司', '廣告', '國貿', '服飾', '招聘', '創業', '新聞', '獸藥', '體育節', '母嬰', '還沒定', '農村信用合作社', '人力', '國際貨運', '職業技能', '智慧財產權', '酒店', '理髮', '中財', '經營', '體檢', '證券', '賓館', '科貿', '經銷', '個人', '報社', '宇辰世紀', '旅遊', '展覽', '演出', '宇辰網', '出版社', '媒體', '認證諮詢', '電子商務', '孵化', '化妝', '職業學院', '拍賣', '葬', '雜貨', '五金', '傳媒', '配件店', '應用服務', '媽媽', '助理', '新華書店', '衛視', '路店', '駕駛員培訓', '道具', '世強', '小賣部', '待業', '醫院', '外貿轉賣', '培訓', '會所', '趣味', '門診', '資產管理', '日用品', '……', '資本', '禮儀', '商貿', '供電局', '管理局', '文化', '使用者', '協會', '政府', '商旅', '展覽會', '個體戶', '食物', '小學', '雲知聲', '地鐵店', '信用社', 'CCTV', '園林', '墓', '學校', '雜誌', '區塊鏈', '商店', '俱樂部', '財富', '食品', '股權', '便利店', '投資', '機關', '租賃', '茶業', '農夫', '種業', '職業技術學院', '連鎖', '政界', '金屬', '電商', '政治', 'Suffice Ind. Tech. Ltd.', '裝潢', '犬業', '城管', '纖維', '新媒體', '交易中心', '經貿', '分店', '報紙', '#NAME?', '質量檢測', '社群', '資訊', '勞務', '擔保'}

print(e)
複製程式碼

然後直接執行該原始檔, 就得到了以下報錯:

python test.py 

  File "test.py", line 3
SyntaxError: Non-UTF-8 code starting with '\xe7' in file test.py on line 3, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
複製程式碼

然後我嘗試著加上宣告原始檔編碼的 Shebang # -*- coding: utf8 -*- 就好使了.

這就很有意思了, 為什麼呢? 抱著有沒有大神的回覆繼續往下翻, 果然, 大腿出現了.

大腿

研究了一下 Python3.6.6 的原始碼,我認為是Python在解析原始碼時出問題了,涉及到的程式碼在decoding_fgets。

Python 解析原始碼時通過 decoding_fgets 讀取一行原始碼至 buffer 中,decoding_fgets 可能使用兩種方式從檔案中讀取原始碼:

1.假如在檔案開頭指定了檔案編碼或者檔案包含 utf-8 BOM,則使用 io.open以該編碼開啟檔案,使用 readline 方法讀取一行原始碼。

2.假如沒有指定編碼,則使用 Py_UniversalNewlineFgets 讀取一行原始碼。

使用方式1讀取原始碼沒有任何問題。
當使用方式2時,Python 會通過valid_utf8檢查讀取到的程式碼的編碼是不是合法的 utf8,一般情況下這也沒有問題,但是 buffer 的初始長度是1024位元組,如果一行程式碼太長(超過1023位元組),則需要分多次讀取,buffer 長度每次遞增1024。運氣差的情況下,分次讀取時正好把一個漢字的位元組給切分開,這就會導致編碼錯誤。

然後我順著大腿的思路給程式碼 Format 了一下, 果然可行...

e = {'合作社', '經濟', '財稅', '賬', '旅行社', '維修', '押運', '管理諮詢', '基業', '養生', '雕塑', '上海警', '首飾', '商行', '電腦', '人才網', '素食', '日用製品',
     '鋁製品', '百貨', '銀行', '產業園', '商學院', '駕駛培訓', '生活', '潛水服務', '電視臺', '貿易', '副食', '鋁業', '風投', '財經', '警察協會', '試驗', '展會',
     '金融', '陶藝', '營業部', '眼鏡', '物業', '飛手', '實驗', '幼教', '超市', '植保機使用者', '農服', '教育', '會展', '銷售', '果業', '保險', '生態園', '企業管理',
     '印刷', '電力局', '傳播', '貸', '拆遷', '裝飾', '養老', '人壽', '生活館', '財務', '餐飲', '諮詢服務', '出品', '個體', '辦公', '自由職業', '動漫', '中央',
     '商務', '老年人', '農資', '新華社', '博覽會', '新華網', '珠寶', '航展', '工貿', '專利', '市政', '體驗', '網咖', '水敏紙檢測噴潵效果', '破產', '大申網', '促進會',
     '酒業', '菸草', '家政', '威海警', '自來水', '代理', '售貨', '創意', '基金', '進出口', '融資', '銀聯', '製衣', '暫無', '成人', '影視', 'VC', '英烈',
     '風水', '營銷', '攝影', '小公司', '廣告', '國貿', '服飾', '招聘', '創業', '新聞', '獸藥', '體育節', '母嬰', '還沒定', '農村信用合作社', '人力', '國際貨運',
     '職業技能', '智慧財產權', '酒店', '理髮', '中財', '經營', '體檢', '證券', '賓館', '科貿', '經銷', '個人', '報社', '宇辰世紀', '旅遊', '展覽', '演出', '宇辰網',
     '出版社', '媒體', '認證諮詢', '電子商務', '孵化', '化妝', '職業學院', '拍賣', '葬', '雜貨', '五金', '傳媒', '配件店', '應用服務', '媽媽', '助理', '新華書店',
     '衛視', '路店', '駕駛員培訓', '道具', '世強', '小賣部', '待業', '醫院', '外貿轉賣', '培訓', '會所', '趣味', '門診', '資產管理', '日用品', '……', '資本',
     '禮儀', '商貿', '供電局', '管理局', '文化', '使用者', '協會', '政府', '商旅', '展覽會', '個體戶', '食物', '小學', '雲知聲', '地鐵店', '信用社', 'CCTV',
     '園林', '墓', '學校', '雜誌', '區塊鏈', '商店', '俱樂部', '財富', '食品', '股權', '便利店', '投資', '機關', '租賃', '茶業', '農夫', '種業', '職業技術學院',
     '連鎖', '政界', '金屬', '電商', '政治', 'Suffice Ind. Tech. Ltd.', '裝潢', '犬業', '城管', '纖維', '新媒體', '交易中心', '經貿', '分店', '報紙',
     '#NAME?', '質量檢測', '社群', '資訊', '勞務', '擔保'}

print(e)
複製程式碼

這個時候加不加宣告原始檔編碼的 Shebang 都不會報錯

佩服

當然, 如果按照 PEP8 規範老老實實寫上 宣告原始檔編碼的 Shebang(即使是 Python3 也應該如此), 程式碼規範按照 PEP8 限制下最大長度就不會出現這個問題. 但是, 從Python直譯器讀取原始檔的方面來說, 確實是漲芝士了, 原來是CPython讀取原始檔是這麼讀取的.

相關文章