当前位置: 首页 > 知识库问答 >
问题:

在Python中停止程序并保存数据的最好方法是什么?

裴弘
2023-03-14

我对在Python中运行的程序停止不感兴趣,当然,control c可以做到这一点。我感兴趣的是以下情况:假设您有一个运行5小时的程序。你让它运行了两个小时,然后决定你到目前为止所做的值得保存,但你仍然不想继续。那么,保存数据并退出程序的最佳方式是什么?直到现在,我所做的是在pickle中存储一个布尔值,然后用每个循环打开pickle并检查其值。如果布尔值为true,则程序继续运行,如果为false,则程序停止并保存数据并退出。我可以使用不同的程序更改布尔值。然而,即使pickle仅由一个布尔值组成,它仍然会严重减慢程序的速度,可能高达10倍,因为pickle需要很长时间才能打开。我考虑过其他解决方案,我知道pdb_trace()工具,但我真的不知道如何在这种情况下使用它。我想也许设置一个环境变量会有帮助,但我不太擅长设置环境变量。如有任何建议,将不胜感激。

共有2个答案

曾瀚昂
2023-03-14

对于您的有趣任务,我决定实现一个非常复杂但通用的异步处理任何命令的解决方案。cmds中提供了命令。txt文件,每行一条命令。现在只支持两个命令<code>save</code>和<code>exit</code><代码>保存可能在空格后包含第二个可选参数,即要保存到的文件名(默认为保存.txt)。

如果程序异常退出(没有提供< code>exit命令),工作将保存到临时文件< code>save.txt.tmp。

cmds.txt文件在单独的线程中处理,每秒检查一次文件,检查非常快,因此不占用CPU,检查只是测试文件移动时间是否已更改。每个新命令都应添加到新行的文件末尾,不应删除已处理的行。程序启动命令文件被清除。

主线程只是检查has_cmds bool变量(如果有新命令),它非常快,可以经常完成,例如在处理10-20毫秒等最小任务之后。没有互斥锁,因此所有工作都非常快。

例如,主线程在随机时间点生成任务处理结果,并将结果存储到数组中。在save命令中,此结果数组保存为JSON。

程序将有关它所做的事情的所有信息打印到包含时间戳的控制台中。

要测试程序,接下来要执行以下操作:

  1. 启动程序。它立即开始处理计算工作。
  2. 在任何文本编辑器中打开cmds.txt
  3. 使用保存字符串添加新行。保存文件。
  4. 程序应该打印保存命令被识别、处理并且工作被保存到文件save.txt
  5. 在编辑器保存other.txt中添加另一行。保存文件
  6. 程序应该打印它已将工作保存到save.txt
  7. 添加新行退出并保存。
  8. 程序应该退出。
  9. 尝试再次运行程序。
  10. 尝试在程序控制台中按Ctrl C
  11. 程序应该捕捉到这个键盘中断并说出这一点,工作也被保存到临时文件save.txt.tmp和程序退出。

原因在最简单的情况下只是为了节省键盘中断的工作,应该像这个答案一样完成。

您还可以像在此解决方案中一样通过处理SIGINT优雅地实现进程终止。SIGINT可以通过语法windows-kill-SIGINT PID发送到使用此程序的程序,其中PID可以通过microsoft的pslist获得。

import threading, random, os, json, time, traceback

cmds = []
has_cmds = False
cmds_fname = 'cmds.txt'
save_fname = 'save.txt'
save_fname_tmp = 'save.txt.tmp'

def CurTimeStr(*, exact = False):
    from datetime import datetime
    return (datetime.now(), datetime.utcnow())[exact].strftime(('[%H:%M:%S]', '[%Y-%m-%d %H:%M:%S.%f UTC]')[exact])

def Print(*pargs, **nargs):
    print(CurTimeStr(), *pargs, flush = True, **nargs)
    
def AddCmd(c, *, processed = False):
    global cmds, has_cmds
    cmds.append({**{'processed': threading.Event()}, **c})
    if processed:
        cmds[-1]['processed'].set()
    has_cmds = True
    return cmds[-1]

def ExternalCommandsThread():
    global cmds, has_cmds
    Print('Cmds thread started.')
    first, next_line, mtime = True, 0, 0.
    while True:
        try:
            if first:
                Print(f'Cleaning cmds file "{cmds_fname}".')
                with open(cmds_fname, 'wb') as f:
                    pass
                first = False
            if os.path.exists(cmds_fname) and abs(os.path.getmtime(cmds_fname) - mtime) > 0.0001 and os.path.getsize(cmds_fname) > 0:
                Print(f'Updated cmds file "{cmds_fname}". Processing lines starting from {next_line + 1}.')
                with open(cmds_fname, 'r', encoding = 'utf-8-sig') as f:
                    data = f.read()
                lines = list(data.splitlines())
                try:
                    mtime = os.path.getmtime(cmds_fname)
                    for iline, line in zip(range(next_line, len(lines)), lines[next_line:]):
                        line = line.strip()
                        if not line:
                            continue
                        if line[0] not in ['[', '{', '"']:
                            cmd = line.split()
                        else:
                            cmd = json.loads(line)
                        pargs = []
                        if type(cmd) is list:
                            cmd, *pargs = cmd
                        cmd = {'cmd': cmd, 'pargs': pargs}
                        assert 'cmd' in cmd, 'No "cmd" in command line!'
                        c = cmd['cmd']
                        if c in ['save']:
                            assert len(set(cmd.keys()) - {'cmd', 'fname', 'pargs'}) == 0
                            AddCmd({'cmd': 'save', 'fname': cmd.get('fname', (cmd['pargs'] or [save_fname])[0])})
                        elif c == 'exit':
                            AddCmd({'cmd': 'exit'})
                        else:
                            assert False, f'Unrecognized cmd "{c}"!'
                        Print(f'Parsed cmd "{c}" on line {iline + 1}.')
                        next_line = iline + 1
                except (json.decoder.JSONDecodeError, AssertionError) as ex:
                    traceback.print_exc()
                    Print(f'Failed to parse cmds line {iline + 1} with text "{line}"!')
                except:
                    raise
            for i, c in enumerate(cmds):
                if c is None:
                    continue
                if not c['processed'].is_set():
                    has_cmds = True
                while not c['processed'].wait(10):
                    Print(f'Timed out waiting for cmd "{c["cmd"]}" to be processed, continuing waiting!')
                Print(f'Processed cmd "{c["cmd"]}".')
                cmds[i] = None
                if c['cmd'] == 'exit':
                    Print('Exit cmd. Cmds thread finishes.')
                    return
            has_cmds = False
            time.sleep(1)
        except Exception as ex:
            traceback.print_exc()
            Print(f'Exception ^^^^^ in Cmds thread!')
            AddCmd({'cmd': 'exit'})
            time.sleep(3)

def Main():
    global cmds, has_cmds
    
    Print('Main thread started.')
    
    threading.Thread(target = ExternalCommandsThread, daemon = False).start()
    
    results = []
    
    def SaveWork(fname):
        with open(fname, 'w', encoding = 'utf-8') as f:
            f.write(json.dumps(results, ensure_ascii = False, indent = 4))
        Print(f'Work saved to "{fname}".')
        
    def ProcessCmds():
        # Returns False only if program should exit
        for c in cmds:
            if c is None or c['processed'].is_set():
                continue
            if c['cmd'] == 'save':
                SaveWork(c['fname'])
            elif c['cmd'] == 'exit':
                Print('Exit cmd. Main thread finishes...')
                c['processed'].set()
                return False
            else:
                assert False, 'Unknown cmd "c["cmd"]"!'
            c['processed'].html" target="_blank">set()
        return True

    try:    
        # Main loop of tasks processing
        for i in range(1000):
            for j in range(10):
                if has_cmds and not ProcessCmds(): # Very fast check if there are any commands
                    return # Exit
                # Emulate small work of 0-200 ms long.
                time.sleep(random.random() * 0.2)
                # Store results of work in array
                results.append({'time': CurTimeStr(exact = True), 'i': i, 'j': j})
        assert False, 'Main finished without exit cmd!'
    except BaseException as ex:
        traceback.print_exc()
        Print(f'Exception ^^^^^ in Main thread!')
        SaveWork(save_fname_tmp)
        AddCmd({'cmd': 'exit'}, processed = True)
    
if __name__ == '__main__':
    Main()

示例输出 1:

[08:15:16] Main thread started.
[08:15:16] Cmds thread started.
[08:15:16] Cleaning cmds file "cmds.txt".
[08:15:21] Updated cmds file "cmds.txt". Processing lines starting from 1.
[08:15:21] Parsed cmd "save" on line 1.
[08:15:21] Work saved to "save.txt".
[08:15:21] Processed cmd "save".
[08:15:31] Updated cmds file "cmds.txt". Processing lines starting from 2.
[08:15:31] Parsed cmd "save" on line 2.
[08:15:31] Work saved to "other.txt".
[08:15:31] Processed cmd "save".
[08:15:35] Updated cmds file "cmds.txt". Processing lines starting from 3.
[08:15:35] Parsed cmd "exit" on line 3.
[08:15:35] Exit cmd. Main thread finishes...
[08:15:35] Processed cmd "exit".
[08:15:35] Exit cmd. Cmds thread finishes.

命令文件<code>cmds。与上述输出相对应的txt:

save
save other.txt
exit

示例输出2:

[08:14:39] Main thread started.
[08:14:39] Cmds thread started.
[08:14:39] Cleaning cmds file "cmds.txt".
Traceback (most recent call last):
  File "stackoverflow_64165394_processing_commands_in_prog.py", line 127, in Main
    time.sleep(random.random() * 0.2)
KeyboardInterrupt
[08:14:40] Exception ^^^^^ in Main thread!
[08:14:40] Work saved to "save.txt.tmp".
[08:14:41] Processed cmd "exit".
[08:14:41] Exit cmd. Cmds thread finishes.

保存.txt示例:

[
    {
        "time": "[2020-10-02 05:15:16.836030 UTC]",
        "i": 0,
        "j": 0
    },
    {
        "time": "[2020-10-02 05:15:16.917989 UTC]",
        "i": 0,
        "j": 1
    },
    {
        "time": "[2020-10-02 05:15:17.011129 UTC]",
        "i": 0,
        "j": 2
    },
    {
        "time": "[2020-10-02 05:15:17.156579 UTC]",
        "i": 0,
        "j": 3
    },

    ................
东方震博
2023-03-14

答案包括检查环境中的变量和文件。这些都可以,但你能做到:

try:
  main()
except KeyboardInterrupt:
  save()

或者,如果保存过程与main完成后使用的过程相同,那么一个更健壮的策略将是

try:
  main()
finally:
  save()

在这里,save()将针对任何错误,键盘中断或其他方式运行。如果 main() 成功,它也将运行。

如果你想用一个单独的程序关闭它,你可以发送一个信号。

 类似资料:
  • 我需要根据一些配置数据启动多个独立的周期性任务——总数事先不知道。例如,我想检查具有不同间隔和不同参数的不同目录的内容,其中列表和参数是从配置中读取的。 在我看来,夸克斯调度器只能调度固定的、预先已知的方法。动态/编程调度的最佳方法是什么?<代码>vertx.set周期 是正确的方法还是我应该以某种方式访问Quartz?

  • 我有一个Android应用程序,我想实现一个功能,用户可以上传SVG,然后将SVG保存在firebase实时数据库中,然后其他用户可以看到它并与之交互,我选择SVG而不是常规照片或PNG的原因是因为它非常小,不会消耗存储或数据来加载, 加上它们具有多种显示密度和不同的屏幕比例更好,问题是我不知道这是否可行,如果它是我不知道它将被保存为的格式是什么,因为我有一个保存SVG路径的想法,但没有找到将其转

  • 问题内容: 我上了一堂课,想跟踪学生的统计数据。我打算稍后制作一个GUI来处理这些数据。 我的主要问题是:保存和以后检索此数据的最佳方法是什么? 我已经读过关于pickle和JSON的文章,但是我并没有真正了解它们的工作方式(尤其是关于它们如何保存数据的信息,例如哪种格式和位置)。 问题答案: 对于持久性数据(存储有关学生的信息),数据库是一个不错的选择。如前所述,Python附带了Sqlite3

  • 问题内容: 我有一些将数据存储在页面中的表格。我想找到一种方法来临时保存用户在导航中输入的数据,并保持到确认订单为止。谁能帮我? 问题答案: 这正是会议的目的

  • 问题内容: 我只是在学习Google App Engine,并试图找到一种管理与Google Cloud SQL实例的数据库连接的好方法(如果您还没有使用GC- SQL,那么基本上是在云中使用MySQL,限制)。 我正在将python(2.7)GAE环境与webapp2框架一起使用来处理请求。我知道常见问题解答说,建议每个请求都与数据库建立新的连接,但是我不知道关闭连接的推荐方法是什么。每次我在开

  • 问题内容: 在Python中,我有以下示例类: 如您所见,我有一个简单的“私有”属性“ _attr”和一个用于访问它的属性。有很多代码可以声明一个简单的私有属性,我认为这样声明所有属性并不符合“ KISS”哲学。 因此,如果我不需要特定的getter / setter / deleter,为什么不将我的所有属性都声明为公共属性呢? 我的回答是:因为封装原理(OOP)另有说明! 什么是最好的方法 ?