2018年5月13日 星期日

Python筆記:資料處理

Python3字串

Python3字串是Unicode字元字串,不再是byte陣列字串。

unicodedata模組

1. unicodedata.lookup(case-insensitive name)會回傳一個Unicode字元。
2. unicodedata.name(Unicode character)會回傳一個大寫的字元名稱(name)。

編碼與解碼

和外界交換資料時,我們必須知道以下兩件事:
  •  把Unicode字元字串編碼為byte的方式
  • 把byte解碼為Unicode字元字串的方式
1. 由於UTF-8是Python, Linux, HTML的標準編碼格式,因此在許多方面都建議使用該格式為字串進行編碼。

格式字串(string formatting)

和C/C++很相似的,%s,%x可以用來在字串中加入值,以下列出常用的代號:

%s :字串
%d:十進位整數
%x:十六進位整數
%o:八進位整數
%f:十進位浮點數
%e:指數浮點數
%g:十進位或指數浮點數
%%:印出"%"

使用的方式是 " %s " % 值,用括號包起字串後加上百分符號,然後再加上值(如果有複數的話則用tuple):

>>> "The dog %s is %f year-old." % ("Jim", 10)
The dog Jim is 10 year-old.

如果需要把欄位寬度設為某個值,只要在"%"和類型指定符之間加上數值就可以了,預設正數會向右對齊(左邊以空格填充),負數會向左對齊(右邊填充空格)。如果要設定字元寬度的話,則是再加上小數點後加上數值:

>>> "%10.5d %10.3f %10.4s" % (1041, 91.14141, "Goodddd")
'     01041     91.141       Good'
>>> "%10.3d %10.3f %10.4s" % (1041, 91.14141, "Goodddd")
'      1041     91.141       Good'

從以上第二個指令結果來看,當字元寬度限制被加在整數上,似乎會無效。
我們甚至可以搭配" * "來傳入設定欄位寬度和字元寬度的值,以彈性變更:

>>> "%*.*d %*.*f" % (10, 4,4452, 3,6, 234.2115514)
'      4452 234.211551'

要注意的是,使用%來格式化字串的方式是舊有的方式,如果以Python3撰寫程式的話,建議使用以下介紹的新方式來操作。

使用{}與format做格式字串

廢話不多說,上例子:

>>> '{2} {0} {1}'.format("First", "Second", "Third")
'Third First Second'

大括號中的數字會參考到tuple中相對應index的值。我們也可以在format的引數加上關鍵字參數或是字典:

>>> "{n} {f} {s}".format(n='12', f=2134, s='\u2603')
'12 2134 ☃'

 >>> "{0[n]} {0[f]} {0[s]} {1}".format({'n':53, 'f':3.33, 's':'rwer'}, 'hey')
'53 3.33 rwer hey'


注意到字典範例中的"0"嗎?它代表format中的第一個項目(字典),然後我們再對該參數(視"0"為一個字典類型的變數)取值。

 新方法也可以使用舊方法的一些格式參數:

>>> "{0:<10d} {1:>10.2f} {2:^s}".format(42, 6.231, 'straw')
'42               6.23 straw'

藍色字體的是表示對齊方向,"<"表示向左對齊,"^"表示置中。要注意整數在此就不適用小數點後的字元寬度設定,直譯器會明確地指出錯誤。

>>> "{0:<10.2d} {1:>10.2f} {2:^s}".format(42, 6.231, 'straw')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Precision not allowed in integer format specifier

我們還可以用空格以外的字元來填充欄位空白,只要在":"的後面加上該符和對齊字元(必須)就行了:

>>> "{0:@^25.11s}".format("Hello")
'@@@@@@@@@@Hello@@@@@@@@@@'

二進位資料

二進位的資料在處理上會遇到位元組順序(Big-endian, little-endian)以及整數符號位元(最大位元, most significant bit)的問題,讓我們繼續看下去。

1. byte類型的物件是不可變的,就像byte類型的tuple。
2. 另一種表示二位元資料的類型是可變的,稱作bytearray,就像byte的list一樣。
3. struct模組的unpack(), pack()函式可以用來在byte序列和Python資料之間轉換:

>>> data[1]
80
>>> data[:1]
b'\x89'
>>> data =b'\x89PNG\x00\x35\xff\x02\x51\x41sqty'
>>> data
b'\x89PNG\x005\xff\x02QAsqty'
>>> data[0]
137
>>> data[1]
80
>>> st.unpack('>b',data[:1])
(-119,)
>>> st.unpack('>B',data[:1])
(137,)
>>> st.unpack('>bb',data[:2])
(-119, 80)
>>> st.unpack('>BB',data[:2])
(137, 80)
>>> st.unpack('>L',data[2:6])
(1313275957,)
>>> st.unpack('>L',data[2:7])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
struct.error: unpack requires a bytes object of length 4
>>> st.unpack('>b',data[:1])
(-119,)
>>> st.unpack('<b',data[:1])
(-119,)
>>> st.unpack('<L',data[:4])
(1196314761,)
>>> st.unpack('>L',data[:4])
(2303741511,)

unpack, pack的第一個參數都是用來指示如何解釋第二個參數的模式,">"代表Big endian,也就是我們要指定原本的位元組順序為「以最高位元開頭」,注意到這裡很容易混淆的是endian代表的字義並不是「結尾」的意思,而是「端」的意思。所以Big endian才會是「以最大的那端做開頭」的作法。可以觀察如下範例:

>>> st.pack('>L',154)
b'\x00\x00\x00\x9a'
>>> st.pack('<L',154)
b'\x9a\x00\x00\x00'

基本上網路資料傳輸都是Big endian,這樣的表示法也會和十六位元的讀取順序一致(由左至右)。至於Endian模式之後的"b", "L"則是對資料的格式做限定,"b"代表有號(signed)的單個byte,"L"代表無號(unsigned)的4-bytes長整數(long)。其他的資料格式代號可以參考這裡

4. 內建的binascii模組可以用來在二進位資料與各式各樣的字串格式做轉換,例如base 16, base 64, uuencode等:

>>> import binascii as ba
>>> va = b'909090234234'
>>> va
b'909090234234'
>>> ba.unhexlify(va)
b'\x90\x90\x90#B4'

但如官網文件所述,這是一個比較低階的模組,通常不會直接使用它,而是使用如uu, base64, 或binhex這些模組來處理。