Python diving — Unicode 深入淺出 - Medium

文章推薦指數: 80 %
投票人數:10人

Unicode 的處理對很多人來說,都是心裡面最軟的一塊,大概了解,但是卻又沒這麼清楚,又很難花時間好好理解。

我從工作開始後,也是花了很多學費(?) GetunlimitedaccessOpeninappHomeNotificationsListsStoriesWritePublishedinGoFreightHQPythondiving—Unicode深入淺出Unicode的處理對很多人來說,都是心裡面最軟的一塊,大概了解,但是卻又沒這麼清楚,又很難花時間好好理解。

我從工作開始後,也是花了很多學費(?),踩了很多坑後,才開始對Unicode慢慢有點理解,進一步也能理解為什麼會有這樣子的設計。

以下就用我粗淺的理解來告訴各位帶各位深入淺出Unicode與Python還有Platform的關係吧。

誰適合閱讀本文有Unicode,ASCII,UTF-8,UTF-16,UTF-32,UCS-2,UCS-4的基礎概念對Pythonstr&bytes有基本的認識1.Pythonunicode&bytes這邊先說一下Python3裡的字串可以用兩個型別來表示:(1)str(2)bytes(其實str就是一般所謂的unicode,以下統稱為unicode)。

而unicode跟bytes的關係,相信會讀這篇文章的讀者,應該都對Python3的unicode與bytes的轉換有些經驗,只要在對的時機點做decodeorencode,結果就可以跑了。

沒錯!就是這個概念。

在Python的程式裡,會建議所有「內部」的商業邏輯操作都使用unicode來處理,只有在與「外界」溝通時,才需要轉換成bytes。

這邊可以定義為「外界」的可以有下列幾個,在Python3裡的function參數都只吃bytes而且,傳unicode字串就會exceptionFileI/O(read,write)SystemI/O(stdin,stdout,stderr)BinaryI/O(httpcontent)下面這段說明應該滿清楚的Use.encode()toconverthumantexttobytes.Use.decode()toconvertbytestohumantext.也可以以下圖來呈現說明pythonunicodeencode/decodeconcept概念上來說,就是PythonApplication「內部」核心處理的部分都用以Unicode處理,「外界」的部分,不管是Terminal的stdout/stdin、HTTPcontent、File,只要跟I/O有關,都是以bytes來處理,進來一律decode成unicode,出去一律encode成bytes。

2.char/wchar_t/multi-bytes這邊先跳脫一下Python這個程式語言,畢竟所有的程式語言都有Unicode的設定,這邊主要以C語言為依據。

1個char等於1個byte/8個bit,ASCII編碼只需要7個bit就可以表示128個字元。

而不同國家不同語系可能會有自己的編碼,如繁體中文的Big5、簡體中文的GBK,唯有UTF系列的編碼包含了所有的字元集,這應該就是大家眼中的Unicode。

Unicode的定義,對每個「字」都有一個獨一無二的編號(目前總共有14萬的字),這個編號稱為CodePoint,格式皆為U+Number,例如硬的CodePoint為U+786C,而且連emoji都有編號,😂U+1F602。

(不過,有些例外的是,有些「字」會有多個以上的CodePoint)。

至於這個CodePoint要怎麼存成binary,就是各個編碼的定義了。

各個國家語系的編碼,基本上都只能處理部分的CodePoint,比如Big5只能處理中文的部分(13060個字),而UTF理論上應該都要可以處理所有的字,UTF-8與UTF-16都算是不定長度的編碼。

UTF-32每個字都是4個bytes可以支援的量為2的32次方4,294,967,296。

硬(U+786C)不同的encoding的binary結果對照表在C語言裡的wchar_t就是為了解決/實作unicode的解法,而各個平台的作法也各有所不同。

不同的地方像是(1)長度的不同,UTF-16與UTF-32的差別在於2個bytes或是4個bytes,(2)資料在binarydata裡的擺放方向不同,big-endian或是little-endian。

各個平台不同指的是,Windowswchar_t的使用的是UTF-16,Linuxwchar_t使用的是UTF-32。

而Windows版本的UTF-16,為了支援更多的CodePoint,有做額外的Surrogates處理,讓部分CodePoint可能也超過2個bytes。

[1]而multi-bytes的概念,應該就是wchar_t從Unicode轉換成不同的編碼後可以用char來儲存。

比如硬(U+786C)這個字,wchar_t字串的長度為1,而經過WideCharToMultiByteorwcstombs的function轉換為UTF-8後,char字串的長度會是3,而內容應該為E7A1AC,轉換為Big5後,char字串長度會是2,而內容應該為B577。

3.CPython—PyUnicode/PyBytesPython3一般的字串就是Unicodee.g."Test",bytes在字串前必需加前b的前綴字e.g.b"Test"。

在CPython裡,Unicode定義的名稱皆為PyUnicode_xxx,而bytes的定義名稱皆為PyBytes_xxx。

先看一下PyUnicode官網上面的定義,其實PyUnicode的每一個字元的type即為wchar_t,所以Pythonwchar_t的size(2個bytes或是4個bytes),也跟platform有關。

[2]Py_UNICODEThisisatypedefofwchar_t,whichisa16-bittypeor32-bittypedependingontheplatform.Changedinversion3.3:Inpreviousversions,thiswasa16-bittypeora32-bittypedependingonwhetheryouselecteda“narrow”or“wide”UnicodeversionofPythonatbuildtime.這邊參考一下CPythonsourcecode—unicodeobject.h/*Py_UNICODEwasthenativeUnicodestorageformat(codeunit)usedbyPythonandrepresentsasingleUnicodeelementintheUnicodetype.WithPEP393,Py_UNICODEisdeprecatedandreplacedwithatypedeftowchar_t.*/#definePY_UNICODE_TYPEwchar_t/*Py_DEPRECATED(3.3)*/typedefwchar_tPy_UNICODE;PyBytesObject看一下源碼的定義typedefstruct{PyObject_VAR_HEADPy_hash_tob_shash;charob_sval[1];/*Invariants:*ob_svalcontainsspacefor'ob_size+1'elements.*ob_sval[ob_size]==0.*ob_shashisthehashofthebytestringor-1ifnotcomputedyet.*/}PyBytesObject;這邊可以注意到的是PyBytesObject將資料儲存在ob_sval裡,雖然它的長度只設定了[1],但是實際上它配置的記憶體長度為ob_size+1。

參考下面的CPythonsourcecode-bytesobject.cPyObject_Malloc的size是PyBytesObject_SIZE+size,PyBytesObject_SIZE是struct的長度,size就是bytes內容的長度。

staticPyObject*_PyBytes_FromSize(Py_ssize_tsize,intuse_calloc){PyBytesObject*op;assert(size>=0);if(size==0){returnbytes_new_empty();}if((size_t)size>(size_t)PY_SSIZE_T_MAX-PyBytesObject_SIZE){PyErr_SetString(PyExc_OverflowError,"bytestringistoolarge");returnNULL;}/*InlinePyObject_NewVar*/if(use_calloc)op=(PyBytesObject*)PyObject_Calloc(1,PyBytesObject_SIZE+size);elseop=(PyBytesObject*)PyObject_Malloc(PyBytesObject_SIZE+size);if(op==NULL){returnPyErr_NoMemory();}_PyObject_InitVar((PyVarObject*)op,&PyBytes_Type,size);op->ob_shash=-1;if(!use_calloc){op->ob_sval[size]='\0';}return(PyObject*)op;}4.Python2->Python3的轉變對Python來說,不管2或是3,概念上可以分為Text與Bytes。

[3]Text—Humanreadablestring,Python2為unicode,Python3為strBytes—Binarydatastring,Python2為str,Python3為bytes在Python2時,許多人都遇過的exception就是UnicodeEncodeError,帶給開發者很大的困擾,因為很多人都搞不太懂為什麼會發生這個exception。

主因是str跟unicode可以混用的關係,而且會做自動encodeordecode。

預設的字串都以str來處理,某些built-infunction在需要unicode時就會自動以ASCIIencoding轉換,然後就…悲劇了。

在Python3裡,為了避免Python2的悲劇,所以將許多的built-infunction的處理加上型別的驗證,傳入的參數如果是str而不是bytes的話,就直接噴exception。

許多情境下str與bytes不會自動轉換,必須要自己做encodeordecode才不會有exception發生。

而這也呼應了Python的提倡的精神「Explicitisbetterthanimplicit」[4]>>>f=open('test.txt','wb')>>>f.write('FOO')Traceback(mostrecentcalllast):File"",line1,inTypeError:abytes-likeobjectisrequired,not'str'對CPython來說,Text還是一樣的PyUnicode。

而Bytes從PyString轉變為PyBytes了。

而在概念上bytes更貼近真實世界的設計,bytes代表的不只是「字串」而已了。

以File而言,不同的副檔名都有自己儲存的格式,「文字檔」才有所謂的文字編碼(encoding,e.g.Big5,UTF-8,…),其他的影片檔,圖片檔,聲音檔…等等,你在讀檔案出來時,絕對不可能對它做decode,因為可能會發生UnicodeDecodeError,而且就算幸運的通過了decode,呈現在stdout的內容也只是亂碼而已。

5.結論以CleanArchitecture的概念來說,在程式邏輯上做好boundary的切割,可以避免過於複雜的問題。

所以在所有的內部文字的處理,統一用Unicode處理,只有在跟外部溝通時,再encode成bytes,理論上就可以避掉大部分UnicodeEncodeErrororUnicodeDecodeError的問題了。

你知道我在找你嗎?是的!HardCore團隊因為GoFreight的穩健成長,需要更多高手加入我們,以下是我們目前正在強力徵求的職缺,有興趣的夥伴準備好你的履歷,讓我們一起GoFreight吧!WebDeveloper/SoftwareDeveloper更多職缺Reference[1]WindowsUTF-16—SurrogatesandSupplementaryCharacters[2]PythonUnicodeDocument—Py_UNICODE[3]Python3Portingguilde—String[4]PEP-20TheZenofPython每個軟體開發者都絕對一定要會的Unicode及字元集必備知識淺談電腦編碼與UnicodeStackOverflow—WhyweconvertfromMultiBytetoWideChar?UnicodeTable—可以查找所有Unicode的文字與CodePointUnicodeOrg—FullemojilistMorefromGoFreightHQGoFreight成立沒有很久,但我們很清楚知道自己在做什麼。

憑藉軟體開發與設計的背景與實力,立足台灣放眼世界。

貨物承攬市場是一個充滿古老技術的行業,而GoFreight的目標是將我們擅長的雲端科技導入傳統貨物承攬產業,帶領他們進入網路的時代並且用新創思維來創造傳產的高規格產品。

同時我們也想和擁有著相同目標的高手們分享我們的心得與想法。

ReadmorefromGoFreightHQAboutHelpTermsPrivacyGettheMediumappGetstartedFalldogHsieh25FollowersHardCoreTechnologyFollowHelpStatusWritersBlogCareersPrivacyTermsAboutKnowable



請為這篇文章評分?