古い回答exec
では、これは問題ありませんが、長期的にはスケーラブルではありません。変更を監視するバックグラウンドでマスター/スレーブプロセス関係またはデーモン/サービスのアプローチもありますが、ほとんどはOS固有であるか、同じOSファミリー間で異なります(init.d対systemd対何でも)。
ブートストラップ手法と単純な呼び出しを使用することによる中間点もあるsubprocess.Popen()
ため、元のプログラムを開始したユーザーが実行可能ファイル ( など) を実行する権限を持っていると仮定して/usr/bin/python
も、まったく同じ実行可能ファイルを使用するため、権限エラーなしで動作するはずです。ブートストラップは、最初の起動後に独自のブートストラップによって自分自身をプルする、つまりリスタータを作成して呼び出すメイン プログラムであるためです。
したがって、他の回答に書かれているように、単純なプログラム(再)スターターは次のように書くことができます:
from subprocess import Popen
from time import sleep
def main():
sleep(<delay>)
Popen([<executable path>, *<list of argv>], cwd=<cwd path>)
if __name__ == "__main__":
main()
必要に応じて、(再)スターター ファイルを削除するなど、後でクリーンアップを実行する必要がある場合があります。
import sys
from os import remove
from os.path import realpath
from subprocess import Popen
from time import sleep
def start():
sleep(<delay>)
# unpack -----------------v
Popen([<executable path>, *<list of argv>], cwd=<cwd path>)
def cleanup():
remove(realpath(sys.argv[0]))
def main():
start()
cleanup()
if __name__ == "__main__":
main()
このファイルは、メイン プログラムから呼び出されます。ただし、メインプログラムには例外があり、を利用sys.exit()
したり、OS シグナルによって強制終了されたりする可能性があります。Python は、そのようなイベントの後に何かをシームレスに行うための複数のフックを提供します。そのうちの 1 つはモジュールによって実装されatexit
ます。atexit
は、Python の例外や一部のシグナル ( SIGINT
) (さらなる改善のためのチェックsignal
モジュール) を気にしないため、独自のシグナル ハンドラを実装する前に妥当な選択です。
これにより、プログラムが停止したときに実行される関数を登録できます。その関数は Python では何でもかまいませんので、ファイルを書き込むこともできます。
ファイルの内容自体は、F 文字列、format()
Jinja を使用してテンプレート化することも、メイン プログラム ( restartable.py
) から除外することもできます。また、値argparse
はpython restarter.py --exe <path> --argv <one> [--argv <two>, ...], --cwd <cwd>
. ユースケースと、OS サービス/デーモンまたはマスター/スレーブ プロセスのスポーンと監視を実装する前に、それをどの程度スケーリングするかによって異なります。
以下にサンプルを示します。
# restartable.py
import sys
from atexit import register
# getcwd() is necessary if you want to prevent issues
# with implicitly changing working directory by a mistake
from os import getpid, getcwd
from os.path import exists, realpath, join, dirname
from subprocess import Popen
from tempfile import NamedTemporaryFile
RESTARTER = """
import sys
from atexit import register
from os import remove
from os.path import realpath
from subprocess import Popen
from time import sleep
def start():
# unnecessary, but may provide enough breathing space
# for the previous process to shut down correctly
# alternatively, watch for a file/file contents
# or utilize a socket
sleep({delay})
# repr() on strings, list is passed as is to unpack it properly
# will fail on custom objects / serialization
Popen([{exe!r}, *{argv}], cwd={cwd!r})
def cleanup():
# remove itself because it's always created
# by the main program on exit
remove(realpath(sys.argv[0]))
def main():
# the register() call can be encapsulated in a condition
# so it restarts only in some cases
register(cleanup)
start()
if __name__ == "__main__":
main()
""".lstrip("\n")
def restart():
with NamedTemporaryFile(mode="w", delete=False) as file:
file.write(RESTARTER.format(
delay=5, # 5s until restart
exe=sys.executable,
argv=sys.argv,
cwd=getcwd()
))
# call the restarting program by the Python executable
# which started the main program
Popen([sys.executable, file.name])
def main():
# create a "norestart.txt" in the folder of "restartable.py" to halt
if not exists(join(dirname(realpath(__file__)), "norestart.txt")):
register(restart)
# tail -f log.txt to check it works properly
# or "ps aux|grep python"
with open("log.txt", "a") as file:
file.write(f"Hello, from {getpid()}\n")
if __name__ == "__main__":
main()
注: 一時フォルダーを使用すると失敗する可能性があるため、その場合は に切り替えてjoin(dirname(realpath(__file__)), "restarter.py")
、メイン プログラムから呼び出すだけです。