はじめに
こちらの記事
デバッグ時はprintではなく、Icecreamを使うと便利
を見て自分で似たような機能を作ってみたくなったので書いてみました。
printをオーバーライドすることで既存のプログラムを書き換えることなく、一番上の行にインポート文ないしは定義文を書くだけでIcecreamを使った際のような表示が可能になります(つまり逆にコメントアウトをすればすぐに元に戻せるということです)。
つくったもの
def print(*args, sep=' ', end='\n', file='', flush=False):
import builtins, inspect, re, sys
if not file: file = sys.stdout
fr = inspect.currentframe().f_back
arg_names = re.split(
'\s*,\s*',
re.findall(
'print\(((?:.|\s)+?)\)', ''.join(
inspect.getframeinfo(fr, min(1000,
fr.f_lineno)).code_context))[-1])
builtins.print(*[
f'{k}: {v}' if
not re.match('[\"\'\(\[\{0-9]', k.lstrip()) else str(v)
for k, v in zip(arg_names, args)
],
sep=sep,
end=end,
file=file,
flush=flush)
使用例
なるべくprintと同じような感覚で使えるように、sepやend、fileなどといった引数を取ることができます。
例えば以下のようなプログラムがあったとします。
a = 1
b = "abcABC"
c = [1, 2, 3]
d = {"A": "a", "B": "b", "C": "c"}
e = {1, 1, 2, 3, 4, 4, 4, 5}
print(a, b, c, d, e)
もちろん出力は
1 abcABC [1, 2, 3] {'A': 'a', 'B': 'b', 'C': 'c'} {1, 2, 3, 4, 5}
となりますね。
ここに先程の関数をインポートしてみます。すると、
from printer import print
a = 1
b = "abcABC"
c = [1, 2, 3]
d = {"A": "a", "B": "b", "C": "c"}
e = {1, 1, 2, 3, 4, 4, 4, 5}
print(a, b, c, d, e)
a: 1 b: abcABC c: [1, 2, 3] d: {'A': 'a', 'B': 'b', 'C': 'c'} e: {1, 2, 3, 4, 5}
プログラムを書き換えることなく変数名を表示することができます。
先程説明したとおり、sepなどを指定することで
print(a, b, c, d, e, sep='\n')
a: 1
b: abcABC
c: [1, 2, 3]
d: {'A': 'a', 'B': 'b', 'C': 'c'}
e: {1, 2, 3, 4, 5}
より見やすく出力したり、
with open('log.txt', 'w') as f:
print(a, b, c, d, e, file=f)
外部ファイルに出力するなどといったことも、通常のprint同様可能です。
仕組み
標準ライブラリinspectを使うことで呼び出し元のコードを取得し、そこから正規表現で変数名を抽出、整形して出力しています。
その性質上、
print(a, b, c);print(d, e)
のような書き方をすると上手く変数名を抽出できないのでその点は注意してください(これはIcecreamも同じ)。