はじめに
Pythonをいじっている時に、
「あれ?このシーケンス型には何が入ってんだっけ?」
と脊髄反射でprintを打つ機会はそこそこあると思います。
内包表記の恩恵もあり、ワンライナーでprintを書く事が可能です。
別に無理にワンライナーで書く必要もないとは思うのですが
ワンライナーで書けるならそれにこした事はないですよね。
ともあれ、Python3からはprint文がprint関数になった事で
シーケンス型が容易にprintできる形となりました。
ただ、シーケンス型の内包表記を使用したワンライナーprintの
実現方式はPython3においては大きく2つあると思うのです。
「たかがprintだし実現出来るんなら何でもいいだろ」と、
ついつい書き味がバラバラになってしまわぬよう
個人的にはどちらかに統一した方がよいと考えました。
実現方式
その2つとは、
1.printの引数として内包表記を渡す
#hogeというシーケンス型を定義(ここではリストとするが何でもよい)
hoge = [i for i in range(3)]
# なんか色々と処理を書く
# そして時は流れ…
# …hogeって何が入っているんだっけ?
print([a for a in hoge])
#->[0, 1, 2]
2.内包表記の中でprintを呼び出す
#hogeというシーケンス型を定義(ここではリストとするが何でもよい)
hoge = [i for i in range(3)]
# なんか色々と処理を書く
# そして時は流れ…
# …hogeって何が入っているんだっけ?
[print(a) for a in hoge]
#->
#0
#1
#2
です。
一般的なのは圧倒的に1だと思うのですが
2の方式でもできてしまうので
そこに言及しておいてもバチは当たらないだろう、ということで
簡単にですが、色々と見てみます。
検証環境
Python3.7
また、方式1でも2でもprint時に扱う内包表記は便宜上、一律「リスト型内包表記」とします。
見え方
上記の通り、方式1の場合は横に、方式2の場合は縦に結果が表示されます。
# 1
[0, 1, 2]
# 2
0
1
2
性能
lengthが100件から1000万件までのリストを準備し、
方式1と方式2でprintして、その所要時間を計測してみました。
hoge = [i for i in range(100)]
# 方式1
print([a for a in hoge])
# 方式2
[print(b) for b in hoge]
パターン | 100件(s) | 1,000件(s) | 10,000件(s) | 100,000件(s) | 1,000,000件(s) | 10,000,000件(s) |
---|---|---|---|---|---|---|
方式1 | 0.00 (一瞬) |
0.02 | 0.02 | 0.05 | 0.41 | 4.75 |
方式2 | 0.00 (一瞬) |
0.02 | 0.12 | 1.52 | 15.3 | 169 (2分49秒) |
明らかに方式1が速いです。
シーケンス型に最適化されているであろうprint自体の能力もさることながら
改行などの表示に伴うコストが恐らく重いんだろうと推察します。
試しに、printをpprintに変えてやってみました。
(pprintのオプションはなし)
パターン | 100件(s) | 1,000件(s) | 10,000件(s) | 100,000件(s) | 1,000,000件(s) | 10,000,000件(s) |
---|---|---|---|---|---|---|
方式1 (pprint) |
0.00 | 0.02 | 0.19 | 1.97 | 20.8 | 152 (2分32秒) |
方式2 (pprint) |
0.02 | 0.02 | 0.25 | 2.06 | 22.3 | 211 (3分31秒) |
劣化しましたね。
まとめ
printする件数が1万件未満の場合
方式1と方式2のどちらでも性能としては大して変わらないという結果になりました。
ただ、そもそもなんですが、方式2の
[print(b) for b in hoge]
の書き方は個人的に気に入りません。(今更…?)
これは、Pythonコード内で
list()
と書いているようなものでしょう。
文法的には正しいですし実行もできますが
使わないリソースをコード上にデンと置いとくのは如何なものかと。
どうしても使いたいのであれば、
_ = [print(b) for b in hoge]
と書くべきだと思います。
であれば素直に方式1でいいというのが結論です。
print([a for a in hoge])
方式2(是正版)で書くくらいであれば、方式1の方がコードも短いですし。
また、方式1はデフォルトだと横に出てしまいますが
縦に見たいのであれば、printの代わりにpprintを使いましょう。
pprintで多少重くなってもそこまで気にするコストでもないでしょう。
printする件数が1万件以上の場合
10万件、100万件、1000万件と、上記の検証ではテストこそしましたが、
**「printでウン十万件以上、標準出力するような機会はあるのだろうか?」というのが正直な所です。
ファイルやDBなどへの永続化を図った方がよっぽど有意義かつ幸せになれると思うのですが
それでも「いや、俺はprintしてぇんだ!」**という奇特な方がいらっしゃるかもしれません。
そんな方の為に何か残せるものがあるとすれば
上記の通り「性能」でしょうかね。
10万件まではどれも数秒の劣化ですが
100万件以上となると性能が数十秒~数分と目に見えて劣化します。
printの前段としてprint出力対象を特定するために、
実際にビジネスロジックを乗せる事も考えるのであれば、
劣化幅はこれ以上でしょう。
結論、printでもpprintでも方式1の方が速いので
素直に方式1でいきましょう。
さいごに
方式2でもPEP8に怒られるような事はありません。
前述の通り、「list()」としているだけですから。
ただ、実現できるからと言う理由で安易にコーディングして
Pythonicでない形になるくらいであれば、素直にいきましょう、と。
よくよく調べて、素直が一番という結論に達しはしたけれども
当初、_ = [print(i) for i in hoge]
という書き方を知った時に
イキっててちょっといいかもと思ったのは内緒だ。
参考記事
https://qiita.com/Kodaira_/items/2bdff13218e86cfb25bb
https://qiita.com/hiroyuki_mrp/items/a65fe0d284747f825571