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


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

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


1. 吐槽問題

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

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

比如這下面這個 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 存儲的。

行吧,好像是我錯了,我改了下,使用 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 語句后居然可以加 逗號

以上。希望此文能對你有幫助。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM