APIから受け取ったデータを確認しようとして、printで表示してみたことはありませんか。画面には長い1行がずらっと並び、どこからどこまでが1つの辞書なのか、目で追うだけで疲れてしまいます。
データの中身を見るのは、プログラミングでいちばんよくある作業のひとつです。それなのに、見づらいまま我慢している方は意外と多いように思います。
そんなときに役立つのが、Python標準ライブラリのpprintです。pip installは要らず、importするだけで使えます。
今回はpprintの基本から、表示の調整方法、ログへの残し方まで順番に見ていきます。実際の出力もそのまま載せているので、手元で動かしながら読んでみてください。
pprintは、データを見やすく整形して表示する標準ライブラリ¶
pprintは、pretty printの略です。日本語にすると、きれいに表示する、という意味になります。
辞書やリストの中にさらに辞書やリストが入っている状態を、ネストと呼びます。pprintは、このネストした構造を改行とインデントで整理し、人間が読みやすい形にして表示してくれます。
辞書そのものの基本がまだ不安な方は、先にこちらを読んでおくと理解がスムーズです。【関連記事】Pythonの辞書(dict)入門
まずは、普通のprintで表示するとどうなるかを確認してみましょう。
user = {
"name": "佐藤",
"id": 1024,
"roles": ["admin", "editor"],
"profile": {
"city": "東京",
"langs": ["Python", "SQL", "JavaScript"],
"joined": "2024-04-01",
},
}
print(user)
手元のPython 3.11で実行すると、次のように1行で表示されました。
{'name': '佐藤', 'id': 1024, 'roles': ['admin', 'editor'], 'profile': {'city': '東京', 'langs': ['Python', 'SQL', 'JavaScript'], 'joined': '2024-04-01'}}
この程度ならまだ読めます。けれども、実際のAPIのレスポンスはこの何十倍もの長さになることが珍しくありません。
まずはppで表示してみる¶
同じデータを、pprintモジュールのpp関数で表示してみます。書き換えるのはimportとprintの部分だけです。
from pprint import pp
pp(user)
実行結果は次のとおりです。
{'name': '佐藤',
'id': 1024,
'roles': ['admin', 'editor'],
'profile': {'city': '東京',
'langs': ['Python', 'SQL', 'JavaScript'],
'joined': '2024-04-01'}}
キーごとに改行され、profileの中身は一段深くそろえて表示されました。どこに何が入っているのか、ひと目でわかるようになりましたね。
pprintは、1行に収まるものはそのまま1行で表示します。80文字を超えそうなときだけ折り返すのが基本の動きです。
pprintとppの違いは、辞書のキーの並び順¶
pprintモジュールには、名前がよく似た関数が2つあります。昔からあるpprint関数と、Python 3.8で追加されたpp関数です。
両者のいちばん大きな違いは、辞書のキーを並べ替えるかどうかです。同じデータをpprint関数で表示してみましょう。
from pprint import pprint
pprint(user)
結果は次のようになりました。
{'id': 1024,
'name': '佐藤',
'profile': {'city': '東京',
'joined': '2024-04-01',
'langs': ['Python', 'SQL', 'JavaScript']},
'roles': ['admin', 'editor']}
nameより先にidが来ています。pprint関数は、辞書のキーをアルファベット順に並べ替えて表示するのが既定の動きだからです。
一方のppは、辞書に入れた順番のまま表示します。違いを表にまとめておきます。
| 関数 | 追加されたバージョン | 辞書のキーの順番 | sort_dictsの既定値 |
|---|---|---|---|
pprint() |
古くからある | アルファベット順に並べ替える | True |
pp() |
Python 3.8 | 入れた順番のまま | False |
pformat() |
古くからある | アルファベット順に並べ替える | True |
キーの順番は、sort_dictsという引数で切り替えられます。pprint関数でもsort_dicts=Falseを渡せば、ppと同じ並びになります。
どちらを使えばよいか迷ったら、まずはppをおすすめします。APIのレスポンスは返ってきた順番に意味があることも多く、勝手に並べ替わると元のデータと見比べにくいからです。
私は10年ほどエンジニアとして開発に関わってきましたが、以前この並べ替えに気づかず、レスポンスのキーの順番が変わったと勘違いして調査に30分ほど使ったことがあります。表示の都合で並びが変わる場合があると知っているだけで、こうした回り道は防げます。
表示を調整する4つの引数¶
pprintには、表示の仕方を細かく調整する引数が用意されています。ここでは、よく使う4つを順番に紹介します。
まずは、それぞれの引数の役割を表で整理しておきましょう。
| 引数 | 既定値 | できること |
|---|---|---|
width |
80 | 1行の最大文字数の目安を決める |
depth |
None | 何階層目まで表示するかを決める |
indent |
1 | 1階層ごとに下げる幅を決める |
compact |
False | 短い要素を1行に詰めて表示する |
widthで1行の長さを変える¶
widthは、1行に収める文字数の目安です。既定値は80なので、それより狭くすると、より細かく折り返されます。
試しにwidth=40で表示してみます。
pp(user, width=40)
langsのリストまで、要素ごとに改行されるようになりました。
{'name': '佐藤',
'id': 1024,
'roles': ['admin', 'editor'],
'profile': {'city': '東京',
'langs': ['Python',
'SQL',
'JavaScript'],
'joined': '2024-04-01'}}
ひとつ注意したいのは、文字数の数え方です。全角の日本語も1文字として数えられるため、日本語が多いデータでは画面上の見た目が少し横長になることがあります。
depthで深い階層を省略する¶
depthは、何階層目まで表示するかを決める引数です。データが大きすぎて全体像がつかめないときに便利です。
depth=1を指定すると、1階層目より深い部分は省略されます。
pp(user, depth=1)
省略された部分は、リストなら[...]、辞書なら{...}と表示されます。
{'name': '佐藤', 'id': 1024, 'roles': [...], 'profile': {...}}
初めて触るAPIのレスポンスは、まずdepth=1で全体の形を眺めるのがおすすめです。そのあと気になる部分だけを取り出して、詳しく見ていけば迷いません。
indentとcompactで見た目を整える¶
indentは、階層が1つ深くなるごとに下げる幅です。既定値は1で、4にすると入れ子の部分が右へ大きく広がって表示されます。
compactは、数値の並んだ長いリストなどで役立ちます。0から29までのリストをppで表示すると、既定では1要素ずつ縦に30行も並んでしまいます。
compact=Trueを付けると、1行に収まるだけ詰めて表示してくれます。
pp(list(range(30)), compact=True)
実行結果は、わずか2行になりました。
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28, 29]
大きな数字やデータクラスも読みやすくなる¶
pprintは、Pythonのバージョンが上がるたびに少しずつ便利になってきました。ここでは比較的新しい機能を2つ紹介します。
Python 3.10では、underscore_numbersという引数が追加されました。Trueにすると、整数が3桁ごとにアンダースコアで区切られて表示されます。
pp({"sales": 12345678, "count": 1500}, underscore_numbers=True)
実行すると、次のように桁がひと目でわかるようになりました。
{'sales': 12_345_678, 'count': 1_500}
Pythonでは、12_345_678と書いても普通の整数として扱われます。そのため、表示されたものをそのままコードに貼り付けても動きます。
同じくPython 3.10からは、dataclassで作ったオブジェクトもpprintで整形できるようになりました。dataclassについては、こちらで詳しく解説しています。【関連記事】Pythonのdataclassとは?
商品を表すデータクラスのリストを、width=50で表示してみます。
from dataclasses import dataclass
from pprint import pp
@dataclass
class Item:
name: str
price: int
tags: list
items = [
Item("ノートPC", 158000, ["電子機器", "仕事用", "持ち運び"]),
Item("マウス", 3980, ["周辺機器"]),
]
pp(items, width=50)
長い1つ目の商品だけが、引数ごとに折り返されました。
[Item(name='ノートPC',
price=158000,
tags=['電子機器', '仕事用', '持ち運び']),
Item(name='マウス', price=3980, tags=['周辺機器'])]
pformatで、整形した結果を文字列として受け取る¶
ppやpprintは、結果を画面に出力するだけです。整形した結果を変数に入れて使いたいときは、pformat関数を使います。
pformatは、画面には何も表示せず、整形済みの文字列を返します。代表的な使い道は、ログへの出力です。
import logging
from pprint import pformat
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
response = {"status": "ok", "items": [{"id": 1, "qty": 3}, {"id": 2, "qty": 5}]}
logger.debug("APIのレスポンス:\n%s", pformat(response, sort_dicts=False))
pformatはsort_dictsの既定値がTrueなので、元の順番で残したいときはFalseを渡します。ログの基本的な使い方は、こちらにまとめています。【関連記事】Pythonのlogging入門
実務では、外部APIとの連携でトラブルが起きたとき、ログに残ったレスポンスが調査の決め手になります。私のチームでも、1行に詰め込まれたログでは読み解くのに時間がかかったため、pformatで整形して残すように改めたことがありました。
ただし、本番環境で大量のデータを毎回整形すると、ログの量も処理時間も増えてしまいます。DEBUGレベルのログに限って使うなど、場面を選ぶのがおすすめです。
json.dumpsとの使い分け¶
データを見やすく表示する方法としては、jsonモジュールのdumpsにindentを指定するやり方もよく知られています。どちらを使えばよいのか、迷う方も多いのではないでしょうか。
結論から言うと、Pythonのデータをそのまま確認したいならpprint、JSONとして扱いたいならjson.dumpsです。主な違いを表にしてみました。
| 観点 | pprint | json.dumps(indent=2) |
|---|---|---|
| 出力の形式 | Pythonの書き方そのまま | JSONの書き方 |
| 文字列の引用符 | シングルクォート | ダブルクォート |
| NoneやTrue | None、Trueと表示 | null、trueに変換 |
| タプルやセット | そのまま表示できる | タプルはリストに変換、セットはエラー |
| datetimeやdataclass | 表示できる | そのままではエラー |
| 日本語 | そのまま表示 | ensure_ascii=Falseが必要 |
json.dumpsは、JSONに変換できない型が入っているとエラーになります。デバッグ中に中身をちょっと見たいだけなら、何でも表示できるpprintのほうが気楽です。
反対に、表示した結果を別のツールに貼り付けたり、ファイルに保存したりするならjson.dumpsが向いています。JSONの扱い方は、こちらで詳しく紹介しています。【関連記事】PythonでJSONを扱う方法
pprintを使うときの注意点¶
便利なpprintですが、いくつか知っておきたい注意点もあります。最後にまとめて確認しておきましょう。
1つ目は、自作クラスの見た目です。dataclassではない普通のクラスは、そのクラスの__repr__の結果がそのまま使われるため、中身までは整形されません。
2つ目は、自分自身を含むデータです。リストに自分自身を追加するような循環した構造でも、pprintは止まらずに<Recursion on list with id=...>のように表示してくれます。
3つ目は、表示は表示でしかないという点です。pprintはデータの見た目を変えるだけで、元のデータの順番や中身は一切変更しません。
まとめ¶
今回は、ネストした辞書やリストを見やすく表示するpprintを紹介しました。最後にポイントを振り返っておきます。
迷ったら、まずはppを使えば大丈夫です。辞書の順番を変えずに、見やすく整形してくれます。
データが大きいときはdepthで全体像をつかみ、細かすぎるときはwidthやcompactで調整します。整形した結果をログに残したいときは、pformatで文字列として受け取りましょう。
地味な機能ですが、データの中身を正しく読めることは、バグを見つける力に直結します。ぜひ、次にprintを書くときにppを思い出してみてください。
ここまでお読みいただきありがとうございました。