Python中計算字元寬度

來源:互聯網
上載者:User
文章目錄
  • GBK decode
  • East_Asian_Width
  • urwid的解決方案

轉載:http://likang.me/blog/2012/04/13/calculate-character-width-in-python/

最近在用python寫一個CLI小程式,其中涉及到計算字元寬度,目標是以友好的方式將一個長字串截取為等寬的片段。

對於unicode字元,python的len函數可以準確的計算其中所包含的字元個數,但是個數並不代表寬度,如:

>>>len(u'你好a')3

因此無法簡單的使用這種方式來計算寬度。

GBK decode

首先我想到GBK編碼,00–7F範圍內的字元是一位元組編碼,其餘是雙位元組編碼,正好與字元的寬度大體一致,於是有了這樣的投機取巧的辦法(假設取8個寬度):

>>> a = u'hello你好'>>> b=a.encode('gbk')>>> try:...   print b[:8].decode('gbk')... except:...   print b[:7].decode('gbk')... hello你

如代碼所示,首先將unicode的字串進行GBK編碼,然後截取8個位元組的寬度後嘗試用GBK解碼,若解碼失敗,則少截取一個寬度,截取7個位元組後使用GBK解碼。

雖然初步解決了問題,但是這樣做的硬傷很明顯。首先代碼不優雅,以試錯的方式運行;其次GBK所能表示的字元有限,對於大量GBK編碼以外的字元無法支援。

East_Asian_Width

徘徊很久之後,偶然發現 Unicode Character Database標準中有East_Asian_Width 屬性,並有以下可能值:

# East_Asian_Width (ea)ea ; A         ; Ambiguous    不確定ea ; F         ; Fullwidth    全寬ea ; H         ; Halfwidth    半寬ea ; N         ; Neutral      中性ea ; Na        ; Narrow       窄ea ; W         ; Wide         寬

其中除A不確定外,F/H/N/Na/W都能很明確的知道寬度,如果保守起見,將A視為寬度為2的話,則很容易給出單個字元的寬度:

>>> import unicodedata>>> def chr_width(c):...   if (unicodedata.east_asian_width(c) in ('F','W','A')):...     return 2...   else:...     return 1>>> chr_width(u'你')2>>> chr_width(u'a')1

到現在似乎已經可以滿足要求了,但是實際使用中發現屬性為A的字元真不少見,最典型的就是中文的雙引號:

>>> chr_width(u'”')2

在大多數等寬字型中,中文雙引號都是只佔一位寬的,如果一行裡有多個中文雙引號,則累加的誤判寬度將會使截取效果大打折扣,無疑這也不是最好的辦法。

urwid的解決方案

urwid是一個成熟的python終端UI庫,它在curses的基礎之上封裝了類似HTML的控制項用以顯示常值內容,如果有這方面的開發需求,非常推薦此庫,比直接使用curses庫方便很多,非常棒的是它對unicode的文本寬度截取非常準確,讓我大為驚訝,於是翻開它的源碼一探究竟,文本寬度計算方面其核心代碼如下:

widths = [    (126,    1), (159,    0), (687,     1), (710,   0), (711,   1),     (727,    0), (733,    1), (879,     0), (1154,  1), (1161,  0),     (4347,   1), (4447,   2), (7467,    1), (7521,  0), (8369,  1),     (8426,   0), (9000,   1), (9002,    2), (11021, 1), (12350, 2),     (12351,  1), (12438,  2), (12442,   0), (19893, 2), (19967, 1),    (55203,  2), (63743,  1), (64106,   2), (65039, 1), (65059, 0),    (65131,  2), (65279,  1), (65376,   2), (65500, 1), (65510, 2),    (120831, 1), (262141, 2), (1114109, 1),]def get_width( o ):    """Return the screen column width for unicode ordinal o."""    global widths    if o == 0xe or o == 0xf:        return 0    for num, wid in widths:        if o <= num:            return wid    return 1

如代碼所示,首先根據unicode的官方EastAsianWidth文檔整理出字元寬度的範圍表,然後使用unicode代碼查表。使用之前的例子測試:

>>> get_width(ord(u'a'))1>>> get_width(ord(u'你'))2>>> get_width(ord(u'”'))1

完全準確,而且在實際應用中的表現也比較好,是一個理想的解決方案,更多技巧請查閱urwid的old_str_util.py源碼。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.