静かなる名辞

pythonとプログラミングのこと

2019/03/22:TechAcademyがteratailの質問・回答を盗用していた件
2019/03/26:TechAcademy盗用事件 公式発表と深まる疑念



【python】print関数を使いこなそう

ぼくたちは本当のprintを知らない

 pythonのprint関数については、たかがprintと思っている人も多いと思いますが、しかしオプションをすべて言える人はあまりいないと思います。把握しておくと出力の細かい制御をしたいとき役立ちます。

 そこで、printの使いこなしについて書きます。なお、念のために先に断っておくと、新し目のpython3のprint関数の話です。python2のprint文とはまったく異なります。

printのオプション

 ドキュメントによると以下の引数を取ります。

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

組み込み関数 — Python 3.7.3rc1 ドキュメント

 *objectsは任意個のオブジェクトをprintできることを示します。sep=' 'は、オブジェクトの区切り文字が半角スペースになるということです。これは*objectsに渡したオブジェクトの数-1個表示されます。また、end='\n'は行末に何を置くかを指定しており、デフォルトは改行です。これは行末に1つだけ置かれます。

 特殊なのはfile引数とflush引数です。file引数は出力ストリームに対応し、デフォルトのsys.stdoutはいわゆる標準出力です。任意のファイルオブジェクトを与えられるので、ファイルに書き込むといった用途に使えます。flushはいわゆる出力バッファのフラッシュの挙動になります。何もしなければ標準出力は改行時にフラッシュされますが、これをTrueにするとprintを呼ぶたびにフラッシュされるようになります。

sepの使い方

 何も指定しないと半角スペースになるので、次のような見慣れた挙動になります。

>>> print(1, 2, 3)
1 2 3

 空の文字列にすることで間を詰めることが可能です。

>>> print(1, 2, 3, sep="")
123

 あるいは好きな文字列を渡すことも可能です。

>>> print(1, 2, 3, sep=",")
1,2,3
>>> print(1, 2, 3, sep="***")
1***2***3

 リストの要素を適当な区切り文字で区切って表示したいというような場合、str.joinで頑張るより、リストのアンパックとprintのsep引数を組み合わせて使った方がスマートだったりします。

>>> l = [1, 2, 3]
>>> print(",".join(map(str, l)))  # こちらの場合は型変換も必要
1,2,3
>>> print(*l, sep=",")  # これだけ
1,2,3

 裏を返せば表示形式はすべて__str__任せになるので、細かい出力フォーマットは指定できないということでもあるのですが、簡易的に使いたい場合はsepの方が便利です。

endの使い方

 改行させたくないときにendを空文字列にする、というのがよくあるユースケースです。

>>> for x in range(4):
...     print(x)
... 
0
1
2
3
>>> for x in range(4):
...     print(x, end="")
... 
0123

 最後に一回改行するために、もう一回printを呼ぶと良いと思います。

 プログレスバー的な進捗表示を簡単にやりたいとき重宝しますが、それっぽく見せるためには後述のflush引数も併用する必要があります。

fileの使い方

 fileはたとえばファイル出力に使えます。

>>> with open("hoge.txt", "w") as f:
...     for x in range(4):
...         print(x, file=f)
... 
>>> (ctrl-dでpythonを終了)
$ cat hoge.txt
0
1
2
3

 その気になればcsvファイルの文字列を手動で作って吐き出すといった用途に使えます。あるいはソケットを叩いたりするのにも使えると思います。ただ、濫用しすぎないことをおすすめします(通常は他に良い方法がある)。

flushの使い方

 flushは前述した通りendと組み合わせると便利です。

>>> import time
>>> for _ in range(10):
...     print("*", end="")
...     time.sleep(0.5)
... 
**********

 進捗表示をやりたくてこんなコードを書く人も多いと思いますが、これは期待通り動作しないと思います。確かに最終的な結果は同じでも、5秒ほどしてから出力が一気に出てくるという挙動になるはずです。これはsys.stdoutが改行されるまでフラッシュされないからです。

 flush=Trueで解決します。

>>> import time
>>> for _ in range(10):
...     print("*", end="", flush=True)
...     time.sleep(0.5)
... 
**********

 これでおよそ0.5秒おきに1つずつ表示されるはずです。

本当のprintを理解したあとの感想

 print一つとっても、意外といろいろな使い方があるということがわかるかと思います。出力の制御にけっこう使えます。

 こういう仕様はすべて公式ドキュメントに書いてあります。ドキュメントに普段から慣れ親しんでおくと、スムーズにコーディングできる、行き詰まったとき打開策が思い浮かぶ、ライバルと差が付けられる、といった利益があります。ドキュメントを読むのに慣れていない方は、printなど単純なものから読んでみると楽しいと思います。