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這些模組來處理。<span class="pre"><br></span>