旧的答案利用exec
这很好,但从长远来看是不可扩展的。还有一种主/从进程关系或后台守护程序/服务的方法,它们将监视更改,但主要是特定于操作系统的,甚至在同一操作系统系列(init.d vs systemd vs)之间也不同。
通过使用引导技术和简单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()
,可能被操作系统信号杀死等等。Python 提供了多个钩子,如何在这样的事件之后无缝地做某事,其中一个是由atexit
模块实现的。atexit
也不关心 Python 异常和一些信号 ( SIGINT
) (用于进一步改进检查signal
模块),因此在实现自己的信号处理程序之前这是一个合理的选择。
这允许您注册一个将在程序停止后执行的函数。该函数可以是 Python 中的任何内容,因此它也可以写入文件。
文件内容本身可以使用 F 字符串、format()
.、Jinja 进行模板化,甚至可以将其保留在主程序之外 ( restartable.py
),甚至可以通过 CLI + 提供值,argparse
例如python restarter.py --exe <path> --argv <one> [--argv <two>, ...], --cwd <cwd>
. 一切都取决于用例以及在实现操作系统服务/守护程序或主/从进程生成+监视之前您希望将其扩展多远。
这是一个示例:
# 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")
并从主程序中调用它。