著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
Python 3には、外部プログラムを実行し、Pythonコードでそれらの出力を読み取るためのサブプロセスモジュールが含まれています。
Pythonコード内からコンピューター上で別のプログラムを使用する場合は、subprocess
が役立つ場合があります。 たとえば、Pythonコード内から git を呼び出して、git
バージョン管理で追跡されているプロジェクト内のファイルを取得したい場合があります。 コンピューターでアクセスできるプログラムはすべてsubprocess
で制御できるため、ここに示す例は、Pythonコードから呼び出す可能性のあるすべての外部プログラムに適用できます。
subprocess
にはいくつかのクラスと関数が含まれていますが、このチュートリアルでは、subprocess
の最も便利な関数の1つであるsubprocess.runについて説明します。 そのさまざまな使用法と主なキーワード引数を確認します。
前提条件
このチュートリアルを最大限に活用するには、Python3でのプログラミングにある程度精通していることをお勧めします。 必要な背景情報については、次のチュートリアルを確認できます。
外部プログラムの実行
subprocess.run
関数を使用して、Pythonコードから外部プログラムを実行できます。 ただし、最初に、subprocess
およびsys
モジュールをプログラムにインポートする必要があります。
import subprocess import sys result = subprocess.run([sys.executable, "-c", "print('ocean')"])
これを実行すると、次のような出力が表示されます。
Outputocean
この例を確認してみましょう。
- sys.executable は、プログラムが最初に呼び出されたPython実行可能ファイルへの絶対パスです。 たとえば、
sys.executable
は/usr/local/bin/python
のようなパスである可能性があります。 subprocess.run
には、実行しようとしているコマンドのコンポーネントで構成される文字列のリストが表示されます。 渡す最初の文字列はsys.executable
であるため、subprocess.run
に新しいPythonプログラムを実行するように指示しています。-c
コンポーネントは、python
コマンドラインオプションであり、Pythonプログラム全体を含む文字列を渡して実行できます。 この例では、文字列ocean
を出力するプログラムを渡します。
subprocess.run
に渡すリスト内の各エントリは、スペースで区切られていると考えることができます。 たとえば、[sys.executable, "-c", "print('ocean')"]
は大まかに/usr/local/bin/python -c "print('ocean')"
に変換されます。 subprocess
は、基になるオペレーティングシステムで実行しようとする前に、コマンドのコンポーネントを自動的に引用することに注意してください。たとえば、スペースを含むファイル名を渡すことができます。
警告:信頼できない入力をsubprocess.run
に渡さないでください。 subprocess.run
にはコンピューター上で任意のコマンドを実行する機能があるため、悪意のある攻撃者がそれを使用して予期しない方法でコンピューターを操作する可能性があります。
外部プログラムからの出力のキャプチャ
subprocess.run
を使用して外部プログラムを呼び出すことができるようになったので、そのプログラムからの出力をキャプチャする方法を見てみましょう。 たとえば、このプロセスは、git ls-files
を使用して、現在バージョン管理下に保存されているすべてのファイルを出力する場合に役立ちます。
注:このセクションに示されている例には、Python3.7以降が必要です。 特に、capture_output
およびtext
キーワード引数は、2018年6月にリリースされたPython3.7で追加されました。
前の例に追加しましょう:
import subprocess import sys result = subprocess.run( [sys.executable, "-c", "print('ocean')"], capture_output=True, text=True ) print("stdout:", result.stdout) print("stderr:", result.stderr)
このコードを実行すると、次のような出力が返されます。
Outputstdout: ocean stderr:
この例は、最初のセクションで紹介した例とほぼ同じです。ocean
を印刷するためのサブプロセスをまだ実行しています。 ただし、重要なのは、capture_output=True
およびtext=True
キーワード引数をsubprocess.run
に渡すことです。
subprocess.run
は、result
にバインドされているsubprocess.CompletedProcessオブジェクトを返します。 subprocess.CompletedProcess
オブジェクトには、外部プログラムの終了コードとその出力に関する詳細が含まれています。 capture_output=True
は、result.stdout
およびresult.stderr
が外部プログラムからの対応する出力で埋められることを保証します。 デフォルトでは、result.stdout
とresult.stderr
はバイトとしてバインドされますが、text=True
キーワード引数は、代わりにバイトを文字列にデコードするようにPythonに指示します。
出力セクションでは、stdout
はocean
(およびprint
が暗黙的に追加する末尾の改行)であり、stderr
はありません。
stderr
の空でない値を生成する例を試してみましょう。
import subprocess import sys result = subprocess.run( [sys.executable, "-c", "raise ValueError('oops')"], capture_output=True, text=True ) print("stdout:", result.stdout) print("stderr:", result.stderr)
このコードを実行すると、次のような出力が返されます。
Outputstdout: stderr: Traceback (most recent call last): File "<string>", line 1, in <module> ValueError: oops
このコードは、ValueError
をすぐに発生させるPythonサブプロセスを実行します。 最終的なresult
を検査すると、stdout
には何も表示されず、stderr
にはValueError
のTraceback
が表示されます。 これは、デフォルトでPythonが未処理の例外のTraceback
をstderr
に書き込むためです。
不正な終了コードで例外を発生させる
実行するプログラムが不正な終了コードで終了した場合に、例外を発生させると便利な場合があります。 ゼロコードで終了するプログラムは成功したと見なされますが、ゼロ以外のコードで終了するプログラムはエラーが発生したと見なされます。 例として、このパターンは、実際にはgit
リポジトリではないディレクトリでgit ls-files
を実行した場合に例外を発生させたい場合に役立ちます。
check=True
キーワード引数をsubprocess.run
に使用して、外部プログラムがゼロ以外の終了コードを返した場合に例外を発生させることができます。
import subprocess import sys result = subprocess.run([sys.executable, "-c", "raise ValueError('oops')"], check=True)
このコードを実行すると、次のような出力が返されます。
OutputTraceback (most recent call last): File "<string>", line 1, in <module> ValueError: oops Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/subprocess.py", line 512, in run raise CalledProcessError(retcode, process.args, subprocess.CalledProcessError: Command '['/usr/local/bin/python', '-c', "raise ValueError('oops')"]' returned non-zero exit status 1.
この出力は、エラーが発生したサブプロセスを実行したことを示しています。このサブプロセスは、端末のstderr
に出力されます。 次に、subprocess.run
は、メインのPythonプログラムで私たちに代わってsubprocess.CalledProcessError
を忠実に作成しました。
または、subprocess
モジュールには、 subprocess.CompletedProcess.check_returncode メソッドも含まれています。これを呼び出すと、同様の効果が得られます。
import subprocess import sys result = subprocess.run([sys.executable, "-c", "raise ValueError('oops')"]) result.check_returncode()
このコードを実行すると、次のようになります。
OutputTraceback (most recent call last): File "<string>", line 1, in <module> ValueError: oops Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/subprocess.py", line 444, in check_returncode raise CalledProcessError(self.returncode, self.args, self.stdout, subprocess.CalledProcessError: Command '['/usr/local/bin/python', '-c', "raise ValueError('oops')"]' returned non-zero exit status 1.
check=True
をsubprocess.run
に渡さなかったため、プログラムがゼロ以外のコードで終了した場合でも、subprocess.CompletedProcess
インスタンスをresult
に正常にバインドできました。 ただし、result.check_returncode()
を呼び出すと、完了したプロセスが不正なコードで終了したことが検出されるため、subprocess.CalledProcessError
が発生します。
タイムアウトを使用してプログラムを早期に終了する
subprocess.run
には、timeout
引数が含まれており、実行に時間がかかりすぎる場合に外部プログラムを停止できます。
import subprocess import sys result = subprocess.run([sys.executable, "-c", "import time; time.sleep(2)"], timeout=1)
このコードを実行すると、次のような出力が返されます。
OutputTraceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/subprocess.py", line 491, in run stdout, stderr = process.communicate(input, timeout=timeout) File "/usr/local/lib/python3.8/subprocess.py", line 1024, in communicate stdout, stderr = self._communicate(input, endtime, timeout) File "/usr/local/lib/python3.8/subprocess.py", line 1892, in _communicate self.wait(timeout=self._remaining_time(endtime)) File "/usr/local/lib/python3.8/subprocess.py", line 1079, in wait return self._wait(timeout=timeout) File "/usr/local/lib/python3.8/subprocess.py", line 1796, in _wait raise TimeoutExpired(self.args, timeout) subprocess.TimeoutExpired: Command '['/usr/local/bin/python', '-c', 'import time; time.sleep(2)']' timed out after 0.9997982999999522 seconds
実行しようとしたサブプロセスは、time.sleep関数を使用して2
秒間スリープしました。 ただし、timeout=1
キーワード引数をsubprocess.run
に渡して、1
秒後にサブプロセスをタイムアウトしました。 これは、subprocess.run
への呼び出しが最終的にsubprocess.TimeoutExpired
例外を発生させた理由を説明しています。
subprocess.run
へのtimeout
キーワード引数は概算であることに注意してください。 Pythonは、timeout
秒の後にサブプロセスを強制終了するために最善を尽くしますが、必ずしも正確であるとは限りません。
プログラムへの入力の受け渡し
プログラムは、入力がstdin
を介して渡されることを期待する場合があります。
subprocess.run
へのinput
キーワード引数を使用すると、サブプロセスのstdin
にデータを渡すことができます。 例えば:
import subprocess import sys result = subprocess.run( [sys.executable, "-c", "import sys; print(sys.stdin.read())"], input=b"underwater" )
このコードを実行すると、次のような出力が返されます。
Outputunderwater
この場合、バイトunderwater
をinput
に渡しました。 ターゲットサブプロセスは、 sys.stdin を使用して、渡されたstdin
(underwater
)を読み取り、出力に出力しました。
input
キーワード引数は、複数のsubprocess.run
呼び出しを連鎖させて、あるプログラムの出力を別のプログラムの入力として渡す場合に役立ちます。
結論
subprocess
モジュールは、Python標準ライブラリの強力な部分であり、外部プログラムを実行してその出力を簡単に検査できます。 このチュートリアルでは、subprocess.run
を使用して外部プログラムを制御し、入力をプログラムに渡し、出力を解析し、リターンコードを確認する方法を学びました。
subprocess
モジュールは、このチュートリアルでは取り上げなかった追加のクラスとユーティリティを公開します。 ベースラインができたので、サブプロセスモジュールのドキュメントを使用して、他の利用可能なクラスとユーティリティについて詳しく知ることができます。