PythonのSubprocessで別のプログラムを実行したい。
同期と非同期処理ってどうやるの?
こんな悩みを解決します。
- PythonのSubprocessの使い方
- PythonからPython(別のファイルもOK)を実行する(標準出力を取得)
- Subprocessの同期と非同期の処理の違いと実装方法
PythonからPythonファイル(別のファイルでもOK)を実行する際に、Subprocess が使えそうだったので、ゴニョゴニョ触ってみたときの記録です。
環境
$ python3 -V
Python 3.7.2
今回は2つのファイルが登場します。
- main.py
- sub.py
main.py から sub.py を呼び出し、sub.py の結果を main.py に返すコードとなっています。
同期処理
同期処理で結果を待つ場合は、subprocess.run を使います。
main.py の具体的なコードは次の通りです。
import subprocess
from subprocess import PIPE
## 同期
print('同期処理でsub.pyを実行中')
proc = subprocess.run(['python', 'sub.py'], stdout=PIPE, stderr=PIPE)
print('同期')
print(proc.stdout.decode('utf-8').split('\n'))
print(proc.stderr.decode('utf-8').split('\n'))
sub.py は次の通りです。
import time
time.sleep(5)
print('ok!!')
5秒間待ってから「ok!!」をプリントする処理になっています。
メインプログラムの main.py を実行すると次のようになります。
$ python3 main.py
同期処理でsub.pyを実行中
###ここから下は5秒後にプリントされる###
同期
['ok!!', ''] ←標準出力
[''] ←標準エラー
proc にサブプロセスが格納されます。
標準出力と標準エラーは proc から取得します。
同期処理のため、subprocess.run 以降のプログラムは sub.py の処理が終わるまで実行されないことがわります。
非同期処理(タイムアウト15秒)
非同期処理では subprocess.Popen を使います。
非同期処理のため結果が返ってくるまでは別の処理を走らせておくことが可能です。
同期処理の subprocess.run とは違い下記の2つを利用します。
- Popen:非同期処理のサブプロセスを起動
- communicate:サブプロセスの終了をまち、結果を受け取る
具体的なコードは次のようになります。
## 非同期 15s
proc = subprocess.Popen(['python', 'sub.py'], stdout=PIPE, stderr=PIPE)
print('非同期処理でsub.pyを実行中')
print('結果が帰ってくるまでの間にやりたいことができるよ')
print('非同期')
try:
# タイムアウトは15秒以上の処理だった場合
outs, errs = proc.communicate(timeout=15)
except subprocess.SubprocessError:
proc.kill()
outs, errs = proc.communicate()
print(outs.decode('utf-8').split('\n'))
print(errs.decode('utf-8').split('\n'))
main.py を実行すると次の結果となります。
$ python3 main.py
非同期処理でsub.pyを実行中
結果が帰ってくるまでの間にやりたいことができるよ
非同期 ←ここまで一気にプリントされます。(同期処理では5秒後にプリントされていました。)
['ok!!', ''] ←標準出力:5秒後にプリントされる
[''] ←標準出力:5秒後にプリントされる
subprocess.Popen を使うことで結果が返ってくるまでに別の処理を実行できます。
proc に格納されたサブプロセスの結果は proc.communicate で受け取ります。
非同期処理(タイムアウト3秒)
今回 sub.py の方では5秒間のスリープをいれているため、communicate の timeout を3秒に設定するとタイムアウトで例外処理のプロセスが走ります。
## 非同期 3s
proc = subprocess.Popen(['python', 'sub.py'], stdout=PIPE, stderr=PIPE)
print('非同期')
try:
# タイムアウトは3秒以上の処理だった場合
outs, errs = proc.communicate(timeout=3)
except subprocess.TimeoutExpired:
proc.kill()
outs, errs = proc.communicate()
print('タイムアウト発生')
print(outs.decode('utf-8').split('\n'))
print(errs.decode('utf-8').split('\n'))
main.py を実行すると次の結果となります。
$ python3 main.py
非同期
タイムアウト発生←タイムアウトが発生して例外処理のプリント文が実行
['']
['']
以上のようにサブプロセスで想定以上に時間がかかった場合、例外をキャッチし次に実行すべきコードを記述できます。
また、タイムアウトが発生した場合子プロセスは kill されません。適切にクリーンアップを行うために、正常に動作するアプリケーションは子プロセスを kill して通信を終了しましょう。