求你了,別再使用 pprint 打印字典了

首發於微信公衆號:Python編程時光html

在線博客地址:http://python.iswbm.com/en/latest/c02/c02_14.htmlpython


1. 吐槽問題

Python 裏的 pprint 你應該很熟悉了吧?android

隨便在搜索引擎上搜索如何打印漂亮的字典或者格式化字符串時,大部分人都會推薦你使用這貨 。編程

好比這下面這個 json 字符串或者說字典(我隨便在網上找的),若是不格式化美化一下,根本沒法閱讀。json

[{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017 你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"鬥魚271934 走過路過不要錯過,這裏有最好的雞兒"}]

若是你不想看到一堆密密麻麻的字,那就使用大夥都極力推薦的 pprint 看下什麼效果(如下在 Python 2 中演示,Python 3 中是不同的效果)。服務器

>>> info=[{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017 你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"鬥魚271934 走過路過不要錯過,這裏有最好的雞兒"}]
>>> 
>>> from pprint import pprint
>>> pprint(info)
[{'des': '2011-2017 \xe4\xbd\xa0\xe7\x9a\x84\xe9\x93\x81\xe5\xa4\xb4\xe5\xa8\x83\xe4\xb8\x80\xe7\x9b\xb4\xe5\x9c\xa8\xe8\xbf\x99\xe5\x84\xbf\xe3\x80\x82\xe4\xb8\xad\xe5\x9b\xbd\xe6\x9c\x80\xe5\xa4\xa7\xe7\x9a\x84\xe5\xae\x9e\xe5\x90\x8d\xe5\x88\xb6SNS\xe7\xbd\x91\xe7\xbb\x9c\xe5\xb9\xb3\xe5\x8f\xb0\xef\xbc\x8c\xe5\xab\xa9\xe5\xa4\xb4\xe9\x9d\x92',
  'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk',
  'iconUrl': 'app/com.renren.mobile.android/icon.jpg',
  'id': 1580615,
  'name': '\xe7\x9a\xae\xe7\x9a\x84\xe5\x98\x9b',
  'packageName': 'com.renren.mobile.android',
  'size': 21803987,
  'stars': 2},
 {'des': '\xe6\x96\x97\xe9\xb1\xbc271934 \xe8\xb5\xb0\xe8\xbf\x87\xe8\xb7\xaf\xe8\xbf\x87\xe4\xb8\x8d\xe8\xa6\x81\xe9\x94\x99\xe8\xbf\x87\xef\xbc\x8c\xe8\xbf\x99\xe9\x87\x8c\xe6\x9c\x89\xe6\x9c\x80\xe5\xa5\xbd\xe7\x9a\x84\xe9\xb8\xa1\xe5\x84\xbf',
  'downloadUrl': 'app/com.ct.client/com.ct.client.apk',
  'iconUrl': 'app/com.ct.client/icon.jpg',
  'id': 1540629,
  'name': '\xe4\xb8\x8d\xe5\xad\x98\xe5\x9c\xa8\xe7\x9a\x84',
  'packageName': 'com.ct.client',
  'size': 4794202,
  'stars': 2}]

好像有點效果,真的是 「神器」呀。微信

可是你告訴我, \xe4\xbd\xa0\xe7\x9a 這些是什麼玩意?原本想提升可讀性的,如今變成徹底不可讀了。網絡

好在我懂點 Python 2 的編碼,知道 Python 2 中默認(不帶u)的字符串格式都是 str 類型,也是 bytes 類型,它是以 byte 存儲的。app

行吧,好像是我錯了,我改了下,使用 unicode 類型來定義中文字符串吧。函數

>>> info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"鬥魚271934走過路過不要錯過,這裏有最好的雞兒"}]
>>> 
>>> from pprint import pprint
>>> pprint(info)
[{'des': u'2011-2017\u4f60\u7684\u94c1\u5934\u5a03\u4e00\u76f4\u5728\u8fd9\u513f\u3002\u4e2d\u56fd\u6700\u5927\u7684\u5b9e\u540d\u5236SNS\u7f51\u7edc\u5e73\u53f0\uff0c\u5ae9\u5934\u9752',
  'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk',
  'iconUrl': 'app/com.renren.mobile.android/icon.jpg',
  'id': 1580615,
  'name': u'\u76ae\u7684\u561b',
  'packageName': 'com.renren.mobile.android',
  'size': 21803987,
  'stars': 2},
 {'des': u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f',
  'downloadUrl': 'app/com.ct.client/com.ct.client.apk',
  'iconUrl': 'app/com.ct.client/icon.jpg',
  'id': 1540629,
  'name': u'\u4e0d\u5b58\u5728\u7684',
  'packageName': 'com.ct.client',
  'size': 4794202,
  'stars': 2}]

確實是有好點了,可是看到下面這些,我崩潰了,我哪裏知道這是什麼鬼,難道是我太菜了嗎?當我是計算機呀?

u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f'

除此以外,咱們知道 json 的嚴格要求必須使用 雙引號,而我定義字典時,也使用了雙引號了,爲何打印出來的爲何是 單引號?我也太難了吧,我連本身的代碼都沒法控制了嗎?

到這裏,咱們知道了 pprint 帶來的兩個問題:

  1. 無法在 Python 2 下正常打印中文
  2. 無法輸出 JSON 標準格式的格式化內容(雙引號)

2. 解決問題

打印中文

若是你是在 Python 3 下使用,你會發現中文是能夠正常顯示的。

# Python3.7
>>> info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"鬥魚271934走過路過不要錯過,這裏有最好的雞兒"}]
>>> 
>>> from pprint import pprint
>>> pprint(info)
[{'des': '2011-2017你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青',
  'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk',
  'iconUrl': 'app/com.renren.mobile.android/icon.jpg',
  'id': 1580615,
  'name': '皮的嘛',
  'packageName': 'com.renren.mobile.android',
  'size': 21803987,
  'stars': 2},
 {'des': '鬥魚271934走過路過不要錯過,這裏有最好的雞兒',
  'downloadUrl': 'app/com.ct.client/com.ct.client.apk',
  'iconUrl': 'app/com.ct.client/icon.jpg',
  'id': 1540629,
  'name': '不存在的',
  'packageName': 'com.ct.client',
  'size': 4794202,
  'stars': 2}]
>>>

可是不少時候(在公司的一些服務器)你沒法選擇本身使用哪一個版本的 Python,原本我能夠選擇不用的,由於有更好的替代方案(這個後面會講)。

可是我出於獵奇,正好前兩天不是寫過一篇關於 編碼 的文章嗎,我自認爲本身對於 編碼仍是掌握比較熟練的,就想着來解決一下這個問題。

索性就來看下 pprint 的源代碼,還真被我找到了解決方法,若是你也想挑戰一下,不防在這裏停住,本身研究一下如何實現,我相信對你閱讀源碼會有幫助。

如下是個人解決方案,供你參考

寫一個本身的 printer 對象,繼承自 PrettyPrinter (pprint 使用的printer)

而且複寫 format 方法,判斷傳進來的字符串對象是否 str 類型,若是不是 str 類型,而是 unicode 類型,就用 uft8 編碼成 str 類型。

# coding: utf-8
from pprint import PrettyPrinter

# 繼承 PrettyPrinter,複寫 format 方法
class MyPrettyPrinter(PrettyPrinter):
    def format(self, object, context, maxlevels, level):
        if isinstance(object, unicode):
            return (object.encode('utf8'), True, False)
        return PrettyPrinter.format(self, object, context, maxlevels, level)

info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"鬥魚271934走過路過不要錯過,這裏有最好的雞兒"}]

MyPrettyPrinter().pprint(info)

輸出以下,已經解決了中文的顯示問題:

打印雙引號

解決了中文問題後,再來看看如何讓 pprint 打印雙引號。

在實例化 PrettyPrinter 對象的時候,能夠接收一個 stream 對象,它表示你要將內容輸出到哪裏,默認是使用 sys.stdout 這個 stream,也就是標準輸出。

如今咱們要修改輸出的內容,也就是將輸出的單引號替換成雙引號。

那咱們徹底能夠本身定義一個 stream 類型的對象,該對象不須要繼承任何父類,只要你實現 write 方法就能夠。

有了思路,就能夠開始寫代碼了,以下:

# coding: utf-8
from pprint import PrettyPrinter

class MyPrettyPrinter(PrettyPrinter):
    def format(self, object, context, maxlevels, level):
        if isinstance(object, unicode):
            return (object.encode('utf8'), True, False)
        return PrettyPrinter.format(self, object, context, maxlevels, level)

class MyStream():
    def write(self, text):
        print text.replace('\'', '"')

info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"鬥魚271934走過路過不要錯過,這裏有最好的雞兒"}]
MyPrettyPrinter(stream=MyStream()).pprint(info)

嘗試執行了下,個人天,怎麼是這樣子的。

[
{
"des"
: 
2011-2017你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青
,
  "downloadUrl": 
"app/com.renren.mobile.android/com.renren.mobile.android.apk"
,
  "iconUrl": 
"app/com.renren.mobile.android/icon.jpg"
,
  "id": 
1580615
,
  "name": 
皮的嘛
,
  "packageName": 
"com.renren.mobile.android"
,
  "size": 
21803987
,
  "stars": 
2
}
,
 
{
"des"
: 
鬥魚271934走過路過不要錯過,這裏有最好的雞兒
,
  "downloadUrl": 
"app/com.ct.client/com.ct.client.apk"
,
  "iconUrl": 
"app/com.ct.client/icon.jpg"
,
  "id": 
1540629
,
  "name": 
不存在的
,
  "packageName": 
"com.ct.client"
,
  "size": 
4794202
,
  "stars": 
2
}
]

通過一番研究,才知道是由於 print 函數默認會將打印的內容後面加個 換行符

那如何將使 print 函數打印的內容,不進行換行呢?

方法很簡單,可是我相信不少人都不知道,只要在 print 的內容後加一個 逗號 就行。

就像下面這樣。

知道了問題所在,再修改下代碼

# coding: utf-8
from pprint import PrettyPrinter

class MyPrettyPrinter(PrettyPrinter):
    def format(self, object, context, maxlevels, level):
        if isinstance(object, unicode):
            return (object.encode('utf8'), True, False)
        return PrettyPrinter.format(self, object, context, maxlevels, level)

class MyStream():
    def write(self, text):
        print text.replace('\'', '"'),

info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"鬥魚271934走過路過不要錯過,這裏有最好的雞兒"}]

MyPrettyPrinter(stream=MyStream()).pprint(info)

終於成功了,太不容易了吧。

3. 何須折騰

經過上面的一番折騰,我終於實現了我 求之不得 的需求。

代價就是我整整花費了兩個小時,才得以實現,而對於小白來講,可能沒有信心,也沒有耐心去作這樣的事情。

因此我想說的是,Python 2 下的 pprint ,真的不要再用了

爲何我要用這麼 說,由於明明有更好的替代品,人生苦短,既然用了 Python ,固然是怎麼簡單怎麼來咯,何須爲難本身呢,一行代碼能夠解決的事情,恰恰要去寫兩個類,那不是自討苦吃嗎?(我這是在罵本身嗎?

若是你願意拋棄 pprint ,那我推薦你用 json.dumps ,我保證你不再想用 pprint 了。

打印中文

其實沒法打印中文,是 Python 2 引來的大坑,並不能全怪 pprint 。

可是一樣的問題,在 json.dumps 這裏,卻只要加個參數就行了,可比 pprint 簡單得不要太多。

具體的代碼示例以下:

>>> info = [{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"鬥魚271934走過路過不要錯過,這裏有最好的雞兒"}]
>>> 
>>> import json
>>> 
>>> 
>>> print json.dumps(info, indent=4, ensure_ascii=False)
[
    {
        "downloadUrl": "app/com.renren.mobile.android/com.renren.mobile.android.apk", 
        "iconUrl": "app/com.renren.mobile.android/icon.jpg", 
        "name": "皮的嘛", 
        "stars": 2, 
        "packageName": "com.renren.mobile.android", 
        "des": "2011-2017你的鐵頭娃一直在這兒。中國最大的實名制SNS網絡平臺,嫩頭青", 
        "id": 1580615, 
        "size": 21803987
    }, 
    {
        "downloadUrl": "app/com.ct.client/com.ct.client.apk", 
        "iconUrl": "app/com.ct.client/icon.jpg", 
        "name": "不存在的", 
        "stars": 2, 
        "packageName": "com.ct.client", 
        "des": "鬥魚271934走過路過不要錯過,這裏有最好的雞兒", 
        "id": 1540629, 
        "size": 4794202
    }
]
>>>

json.dumps 的關鍵參數有兩個:

  • indent=4:以 4 個空格縮進單位
  • ensure_ascii=False:接收非 ASCII 編碼的字符,這樣才能使用中文

與 pprint 相比 json.dumps 能夠說完勝:

  1. 兩個參數就能實現全部個人需求(打印中文與雙引號)
  2. 就算在 Python 2 下,使用中文也不須要用 u'中文' 這種寫法
  3. Python2 和 Python3 的寫法徹底一致,對於這一點不須要考慮兼容問題

4. 總結一下

原本很簡單的一個觀點,我爲了證實 pprint 實現那兩個需求有多麼困難,花了不少的時間去研究了 pprint 的源碼(各類處理其實仍是挺複雜的),不過好在最後也能有所收穫。

本文的分享就到這裏,閱讀本文,我認爲你能夠獲取到三個知識點

  1. 核心觀點:Python2 下不要再使用 pprint
  2. 若真要使用,且有和同樣的改造需求,能夠參考個人實現
  3. Python 2 中的 print 語句後竟然能夠加 逗號

以上。但願此文能對你有幫助。

關注公衆號,獲取最新干貨!

相關文章
相關標籤/搜索