やはりpythonのsubprocessでcatするのはまちがっている。

やっはろー。

###要約
pythonで`subprocess.run('cat path', shell=True)`なんて書いている人は、僕と一緒に悔い改めて明日から`open()`を使いましょう。

#`subprocess`とは
お詳しい方は読み飛ばしてください。
`subprocess`とはLinuxのOSコマンドをpython上から実行するモジュールです。
Python上でOSコマンドを実行するにはos関数やsystem関数などもありますが現在は`subprocess`が推奨されているそうです。

```python
import subprocess

list = subprocess.run('ls') 
#hoge.py  hoge.sh  hoge.wav
print(list)
#CompletedProcess(args='ls', returncode=0)

#引数にコマンドを渡してあげると実行結果（ステータス）が返ってきます

list = subprocess.run('ls', encoding='utf-8', stdout=subprocess.PIPE).stdout
print(list)
#hoge.py
#hoge.sh
#hoge.wav

#encodingを指定することでutf-8などでエンコードできます
#stdout属性を指定することで標準出力に出力を渡せます

list = subprocess.check_output('ls -a', shell=True, encoding='utf-8')
print(list)
#.hoge
#hoge.py
#hoge.sh
#hoge.wav

#shell=Trueを指定することで空白を含められますが、コマンドインジェクションなどの危険があります
#subprocess.check_output(...) == subprocess.run(..., check=True, stdout=PIPE).stdout
```

こんな感じに色々とOSコマンドを実行できますが、毎回サブプロセスを作成するので処理が遅いです。
イメージとしては、親が本を読むためにわざわざ子供を産んで子供に音読させてるような感じです。
自分で読めば解決するのに、そりゃ重いのも納得ですね。

ちなみに`stdout=subprocess.PIPE`のオプションは音読してね！って意味です。
親は子供の読んでいる本の内容を（第六感で思念を読めない限り）知ることができませんが、音読をしてもらえば知ることができますね。
`subprocess.check_output(...)`は`subprocess.run(..., check=True, stdout=PIPE).stdout`と等価です。
`check=True`属性はコマンドが非ゼロ（エラー）終了した際に`CalledProcessError`例外が送出されるので`exception`なんかで拾ってあげれば例外処理が書けます。

本記事ではディスってますがめっちゃ便利なモジュールですので初めて知られた方は是非使ってみて下さい。
~~処理も普通に使う分にはそこまで重くないです。~~（企画崩壊）

#`subprocess`で`cat`は愚行だった
前述のように、`subprocess.run('cat /hogehoge.txt', shell=True)`とすればhogehoge.txtのテキストコンテンツが取得できますが、テキストを引っ張ってくるためだけにサブプロセスを立ち上げるので処理が遅くなってしまいます。
Pythonでファイルを開くには`open()`という組み込み関数が使えますのでそちらを使いましょう。
超々初歩的なことですが自分は知りませんでした、簡単な使い方は以下の通り。

```python
with open('/hogehoge.txt') as f:
    hoge = f.read()
print(hoge)
#/hogehoge.txtのテキストが表示されます
```

`open()`で開いたものは必ず`close()`で閉じなくてはいけないので、`with`を用いて閉じ忘れをなくしています。
`open()`を閉じた後、ファイルは参照できなくなりますが代入した変数は参照できます。
詳しくは[公式ドキュメント](https://docs.python.org/ja/3/library/functions.html#open)をご覧ください。

#`open()`はどれくらい早いのか
以下のようなスクリプトを書いて検証してみました。

```python
#!/usr/bin/python3.7

import subprocess
import time

#open()
start = time.time() #開始時間取得
for i in range(1000): #1000回繰り返し実行
    with open('/sys/class/thermal/thermal_zone0/temp') as f: #ファイルを開く
        test = f.read() #ファイルのテキストを代入
elapsed_time = time.time() - start #終了時刻を取得して開始時刻との差分を求める
print("Open: {}s".format(elapsed_time)) #経過時間を書き出し

#subprocess
start = time.time()
for i in range(1000):
    test = subprocess.run('cat /sys/class/thermal/thermal_zone0/temp', shell=True, stdout=subprocess.PIPE).stdout
elapsed_time = time.time() - start
print("Subprocess: {}s".format(elapsed_time))

#subprocessでvcgencmd（CPU温度に関して参考記録）
start = time.time()
for i in range(1000):
    test = subprocess.run('vcgencmd measure_temp', shell=True, stdout=subprocess.PIPE).stdout
elapsed_time = time.time() - start
print("Vcgencmd: {}s".format(elapsed_time))
```

実行結果は以下の通りです。

```python
Open: 0.08225083351135254s
Subprocess: 5.188024044036865s
Vcgencmd: 6.032892465591431s
```

なんということでしょう！
あんなに遅かったsubprocessが50倍以上速くなりました。

恐らく経過時間の内訳は大方待機時間だと思いますのでCPU負荷に関しては単純には言えないのですが、別窓から監視していた感じ軽いのは確かです。
数回実行するとばらつきはありますが、ここまで違ってくると`open()`を使おうという気になります。

ちなみに、`subprocess.run`では親プロセスの処理を子プロセスが終了するまで停止させますが、`subprocess.Popen`を用いることで子プロセスをバックグラウンドに回して並列処理が行えます。
`subprocess.Popen`を用いても上記検証では2sほどかかっていたので`open()`が速いのは間違いないのですが、状況によっては差が縮まることもあります。
いずれにせよ今回の目的はテキストファイルの取得ですから並列は避けたいですが…。

本検証は[PythonでラズパイのCPU温度を測定する](https://qiita.com/genshibangou16/items/ffb96765a56ccab9b62d)という記事で扱ったスクリプトの改良をきっかけとしているので参考に`vcgencmd`でも確認してみました。
`vcgencmd`の出力は温度の値が可読性が高くなるように整形されているので、その分遅くなっていると考えられます。

【追記】
本記事の内容をもとに[PythonでCPUを監視するスクリプト](https://qiita.com/genshibangou16/items/90021d4fb65577e954c9)を書きました。

#最後に
思っていたより明確な差が出て驚きました。
もっともっと勉強して「適材適所」を意識したプログラムを書けるようになりたいです。