The old answers utilize exec
which is fine, but not scalable in the long run. There's also an approach of master/slave process relationship or a daemon/service in the background which will watch for the changes but is mostly OS specific or even different between the same family of OSs (init.d vs systemd vs whatever).
There's also a middle ground by using a bootstraping technique and a simple subprocess.Popen()
call thus assuming that the user who started the original program had the permissions to run the executable (such as /usr/bin/python
) should also work without any permission errors due to utilizing the exactly same executable. Bootstraping because it's the main program that's creating and calling the restarter a.k.a. pulling itself by own bootstraps after the initial start.
So a simple program (re)starter can be written like this, as written in the other answers:
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()
Depending on your needs you might want to do some cleanup afterwards such as removing the (re)starter file.
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()
This file would be called from your main program. However, your main program may have an exception, utilize sys.exit()
, may be killed by a OS signal and so on. Python provides multiple hooks how to do something after such an event seamlessly, one of which is implemented by the atexit
module. atexit
doesn't care about Python exceptions and about some signals either (SIGINT
) (for further improvement check signal
module), so it's a reasonable choice before implementing own signal handler(s).
This allows you to register a function that will execute once your program stops. That function can be anything in Python, so it can write a file too.
The file content itself can be templated with F-strings, format()
, Jinja, or can be even kept out of the main program (restartable.py
) and the values might be even provided via CLI + argparse
such as python restarter.py --exe <path> --argv <one> [--argv <two>, ...], --cwd <cwd>
. Everything depending on the use-case and how far do you want to scale it before implementing an OS service/daemon or a master/slave process spawning+watching.
Here is a sample:
# 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()
Note: It might fail by using the temporary folder, so in that case just switch it to the join(dirname(realpath(__file__)), "restarter.py")
and call that from the main program.