从自身内部重新启动python-script

63 人关注

我有一个基于Python的GTK应用程序,它加载了几个模块。它是这样从(linux)终端运行的。

./myscript.py --some-flag setting

在程序中,用户可以下载(使用Git)更新的版本。 如果这些版本存在/被下载,就会出现一个按钮,我希望它能用新编译的内容(包括依赖/进口)重新启动程序。最好还能用 sys.argv 的内容重新启动,以保持所有的标志。

因此,我没有找到/需要的是一个漂亮的重启程序,它可以杀死程序的当前实例,并使用相同的参数启动新的程序。

最好是解决方案也能在Windows和Mac上运行,但这并不是必须的。

2 个评论
可能的做法是,一开始让新进程与旧进程并行运行。理论上,你可以给新进程以旧进程的ID(通过一个额外的命令行选项),并在新进程启动和运行后立即杀死旧进程。
python
deinonychusaur
deinonychusaur
发布于 2012-07-04
15 个回答
Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams
发布于 2022-08-18
已采纳
0 人赞同

You're looking for os.exec*() 命令系列。

要以与最初运行时完全相同的命令行参数重新启动你的当前程序,你可以使用以下方法。

os.execv(sys.argv[0], sys.argv)
    
哇!我不知道这是可能的。谢谢你指出来。
在Python 2.7.9中,这给我一个无效的语法错误。
@clankill3r 命令中的星号是一个通配符,不应该从字面上理解。这是因为有无数个以 "exec "开头的命令,你必须为你的使用情况挑选正确的命令。请看链接。但在这个评论中,一些例子确实会有帮助。
出现异常 PermissionError: [Errno 13] Permission denied ,有什么解决办法吗?
令人敬畏的替代方法是使用。 os.execv(sys.executable, ['python'] + sys.argv) 。这样,你就不需要使用 chmod (以避免看到权限被拒绝的错误),也不需要在脚本的开头添加shebang(以避免看到 OSError: [Errno 8] Exec format error )。
s3ni0r
s3ni0r
发布于 2022-08-18
0 人赞同

我认为这是一个更详细的答案,因为有时你可能最终有太多的开放文件对象和描述符,这可能导致内存问题或网络设备的并发连接。

import os
import sys
import psutil
import logging
def restart_program():
    """Restarts the current program, with file objects and descriptors
       cleanup
        p = psutil.Process(os.getpid())
        for handler in p.get_open_files() + p.connections():
            os.close(handler.fd)
    except Exception, e:
        logging.error(e)
    python = sys.executable
    os.execl(python, python, *sys.argv)
    
我不确定API是否有变化,但对于目前的psutil包(5.4.8)来说,是p.open_files()而不是p.get_open_files()
如果你使用Python 3.6或更高版本,将 except Exception, e: 改为 except Exception as e:
我得到:ERROR:root:[Errno 9] Bad file descriptor
Basic Block
Basic Block
发布于 2022-08-18
0 人赞同

我知道这个解决方案在技术上并不是你所要求的,但如果你想100%确定你释放了所有的东西,或者不想依赖依赖关系,你可以从另一个循环中运行这个脚本。

import os, time
while 1:
    os.system("python main.py")
    print "Restarting..."
    time.sleep(0.2) # 200ms to CTR+C twice

然后你就可以像这样简单地重启main.py。

quit()
    
user285594
发布于 2022-08-18
0 人赞同

更新--上述答案的一些例子,供今后参考

I have runme.sh

#!/bin/bash
kill -9 server.py
python /home/sun/workspace/c/src/server.py &

而我有server.py,我需要重新启动应用程序本身,所以我有。

os.system('runme.sh')

但这并没有通过runme.sh来重启应用程序本身,所以当我使用这种方式时。

os.execl('runme.sh', '')

然后我就能自己重新启动了

Ville
Ville
发布于 2022-08-18
0 人赞同

在Windows下工作 (不含args)

os.startfile(__file__)
sys.exit()
os.startfile(sys.argv[0])
sys.exit()
    
这是一个很好的答案......但我怎样才能防止 py.exe 每次都打开?
Core taxxe
I think you want to rename the file to <name>.pyw which makes that file run without the console.
Kamornik Cola
Kamornik Cola
发布于 2022-08-18
0 人赞同

对我来说,这一部分工作得很好。

def restart():
    import sys
    print("argv was",sys.argv)
    print("sys.executable was", sys.executable)
    print("restart now")
    import os
    os.execv(sys.executable, ['python'] + sys.argv)

I got it here.

谢谢!对我来说也很有效。 除了,我不得不把Python改为Python3,否则在第二次重启时模块没有导入。
Dr ALOUI
Dr ALOUI
发布于 2022-08-18
0 人赞同

我只是对#s3niOr的代码做了一点改进。

在我的例子中,Python文件的路径中存在空格。所以通过添加适当的格式化,问题就可以解决了。

注意,在我的例子中,我的Python文件没有其他参数。所以如果你有其他参数,你必须处理它们。

这就解决了重启一个路径中有空格的Python脚本的问题。

import os
import sys
import psutil
import logging
def restart_program():
    """Restarts the current program, with file objects and descriptors
       cleanup
        p = psutil.Process(os.getpid())
        for handler in p.get_open_files() + p.connections():
            os.close(handler.fd)
    except Exception, e:
        logging.error(e)
    python = sys.executable
    os.execl(python, python, "\"{}\"".format(sys.argv[0]))
    
我=复制/粘贴 :D
mdkb
mdkb
发布于 2022-08-18
0 人赞同

我一直在寻找这方面的解决方案,但在 stack overflow 的帖子中没有发现任何可行的方法。也许有些答案太过时了,比如os.system已经被subprocess取代。我在linux lubuntu 17.10上,使用python 3。

有两种方法对我有效。都是打开一个新的shell窗口,在其中运行main.py脚本,然后关闭旧窗口。

1.使用main.py和一个.sh脚本。

改编自@YumYumYum的方法。 我不需要kill选项,(尽管无论如何它都不能杀死我的Python进程的名字,我在测试时不得不使用 killall python3 来实现它)。

我使用lxterminal,但你可能可以使用任何一种。

在名为restart.sh的文件中(chmod +x以使其可执行)

#!/bin/bash
lxterminal -e python3 /home/my/folder/main.py &

然后在main.py中,当你需要调用它时,使用这个方法

import subprocess
subprocess.run('/home/my/folder/restart.sh', shell=True)
quit()

2.从main.py中

改编自 @Stuffe 方法。这个方法是在main.py脚本的内部,打开一个新的shell窗口,然后运行新的脚本,再从旧的脚本中退出。我不确定它是否需要time.sleep延迟,但我还是使用了它。

import subprocess, time
cmd = 'python3 /home/my/folder/main.py'
subprocess.run('lxterminal -e ' + cmd, shell=True)   
time.sleep(0.2)
quit()
    
user17824785
user17824785
发布于 2022-08-18
0 人赞同

Try this work with Windows:

当你想重新启动脚本时,调用这个函数

import os
def rest():
    os.system('cls')
    script_name = os.path.basename(__file__)
    os.system(script_name)
    
Imagine Breaker
Imagine Breaker
发布于 2022-08-18
0 人赞同

受到@YumYumYum的启发,解决了这个问题 使用restart.sh和os.execl

restart.sh
#!/bin/bash/
pkill -f main.py
python main.py

在你的main.py中加入以下内容

os.excel("restart.sh","")
    
Silvis Sora
Silvis Sora
发布于 2022-08-18
0 人赞同

我使用这个来给用户一个选项,让他们在控制台中重新启动脚本。希望这能对你有所帮助。

def close_restart(self,csvfile):
    choice = input('Do you want to restart the program? Please select \'Y\' if you would like to restart.')
    if choice == 'Y' or choice == 'y':
        print('Restarting now...')
        os.execl(sys.executable, sys.executable, *sys.argv)
    else:
        print('Thank you for using the tool!')
        print('The program will close in 10s...')
        time.sleep(10)

因此,用户可以输入一个选项'Y/N'来重新启动程序或不启动。

Peter Badida
Peter Badida
发布于 2022-08-18
0 人赞同

旧的答案是利用 exec ,这很好,但从长远来看是不可扩展的。还有一种方法是主/从进程关系或后台的守护进程/服务,这将观察变化,但大多是操作系统特定的,甚至在同一系列的操作系统之间也不一样(init.d vs systemd vs whatever)。

也有一个中间地带,通过使用引导技术和简单的 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模块。替换代码6】不关心Python异常,也不关心一些信号(SIGINT)(进一步改进请查看signal模块),所以在实现自己的信号处理器之前,这是一个合理的选择。

这允许你注册一个函数,一旦你的程序停止就会执行。这个函数可以是Python中的任何东西,所以它也可以写一个文件。

文件内容本身可以用F-字符串进行模板化。format()替换代码11】),甚至可以不在主程序中,甚至可以通过CLI+提供值。argparsepython 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")并从主程序中调用即可。

Íhor Mé
Íhor Mé
发布于 2022-08-18
0 人赞同
os.execv(sys.executable, ['python3'] + sys.argv)
os.execv(sys.executable, ['python2'] + sys.argv)
    
dragonfly
dragonfly
发布于 2022-08-18
0 人赞同

我的做法如下。

    def restartApp(self):
        python = sys.executable
        os.execl(python, python, *sys.argv)
    
A P
请解释一下这段代码有什么作用,以帮助OP。
Valerij
Valerij
发布于 2022-08-18
0 人赞同

而不是这样。

from os import execl
from sys import executable, executable, argv
execl(executable, executable, *argv)

我决定自己做一个。

NameProject.py。

system('bash rerun.sh')

rerun.sh:

#!/bin/bash
(killall NameProject.app; cd ..; cd ..; cd ..; open NameProject.app) || (killall python; python NameProject.py)