Python:使用sys.exit或SystemExit的差异和建议

Gia*_*ear 44 python performance coding-style

在线阅读一些程序员使用sys.exit,其他人使用SystemExit.
对不起,基本问题是:

  1. 有什么不同?
  2. 我什么时候需要在函数内使用SystemExit或sys.exit?

ref = osgeo.ogr.Open(reference)
if ref is None:
    raise SystemExit('Unable to open %s' % reference)
Run Code Online (Sandbox Code Playgroud)

要么

ref = osgeo.ogr.Open(reference)
if ref is None:
    print('Unable to open %s' % reference)
    sys.exit(-1)
Run Code Online (Sandbox Code Playgroud)

Fre*_*Foo 28

sys.exit(s)只是简写raise SystemExit(s),正如前者的文档中描述的那样; 试试help(sys.exit).因此,您可以这样做,而不是您的任何一个示例程序

sys.exit('Unable to open %s' % reference)
Run Code Online (Sandbox Code Playgroud)


Ric*_*dle 25

没有实际的区别,但是你的示例代码还有另一个区别 - print转到标准输出,但异常文本转到标准错误(这可能是你想要的).

  • @GrijeshChauhan:它们仍然是一样的。任何一个都会导致当前线程退出。(该博文中提到的“变化”是主线程和后台线程之间的区别,而不是“exit”和“SystemExit”之间的区别。) (2认同)

Per*_*ins 10

除了提升之外,还有3个退出功能SystemExit.

底层的是os._exit,需要1个int参数,并且在没有清理的情况下立即退出.你不太可能想触摸这个,但它就在那里.

sys.exit在sysmodule.c中定义并且只是运行PyErr_SetObject(PyExc_SystemExit, exit_code);,这实际上与直接引发相同SystemExit.在细节方面,提高SystemExit可能更快,因为sys.exit需要LOAD_ATTRCALL_FUNCTIONvs RAISE_VARARGSopcalls.此外,raise SystemExit产生稍小的字节码(少4字节),(如果你使用from sys import exit,sys.exit则额外增加1个字节, 因为预期返回None,所以包括额外的POP_TOP).

最后一个退出函数在REPL中定义site.py,或别名exitquit在REPL中.它实际上是Quitter类的一个实例(所以它可以有一个自定义__repr__,因此可能是最慢的运行.而且,它sys.stdin在提升之前关闭SystemExit,因此建议仅在REPL中使用.

至于如何SystemExit处理,最终会导致VM调用os._exit,但在此之前,它会进行一些清理.它还运行atexit._run_exitfuncs(),运行通过atexit模块注册的任何回调.os._exit直接呼叫绕过该atexit步骤.


pio*_*kuc 7

根据文档,sys.exit(s)实际上是这样raise SystemExit(s),所以它几乎是一样的。


Jon*_*nts 6

我个人的偏好是至少SystemExit提出(或者甚至更好 - 一个更有意义且记录良好的自定义异常)然后尽可能接近"主要"功能,这可能有最后的机会认为它是有效退出与否.sys.exit从设计的角度来看,图书馆/深度嵌入的功能只是简单的讨厌.(一般来说,退出应尽可能"高涨")


ser*_*inc 6

虽然许多答案都回答了这种差异,但 Cameron Simpson 在https://mail.python.org/pipermail/python-list/2016-April/857869.html中提出了一个有趣的观点:

TL;DR:最好只引发“正常”异常,并仅在脚本的顶层使用SystemExitor 。sys.exit

我使用 python 2.7 和 Linux ,如果我可以用 raise SystemExit 替换 sys.exit(1) ,我有一个简单的代码需要建议。

==实际代码==

def main():    
    try:
       create_logdir()
       create_dataset()
       unittest.main()    
     except Exception as e:
       logging.exception(e)
       sys.exit(EXIT_STATUS_ERROR)

if __name__ == '__main__':    main()
Run Code Online (Sandbox Code Playgroud)

==更改代码==

def main():    
    try:
       create_logdir()
       create_dataset()
       unittest.main()    
    except Exception as e:
       logging.exception(e)
       raise SystemExit

if __name__ == '__main__':    
    main()
Run Code Online (Sandbox Code Playgroud)

我个人反对这两种做法。我的首选模式是这样的:

  def main(argv):
    try:
      ...
    except Exception as e:
      logging.exception(e)
      return 1

  if __name__ == '__main__':
    sys.exit(main(sys.argv))
Run Code Online (Sandbox Code Playgroud)

请注意,main() 又恢复为具有正常返回值的正常函数。

另外,我们大多数人都会避免“ except Exception”,而只是让顶层 except 冒泡出来:这样你就可以获得用于调试的堆栈回溯。我同意它可以防止记录异常并导致控制台输出变得更丑陋,但我认为这是一个胜利。如果你确实想记录异常,总是这样:

尝试:... except Exception as e:logging.exception(e) raise

将异常记录到日志中并仍然让它正常冒泡。

“除了异常”模式的问题在于它捕获并 隐藏 每个异常,而不仅仅是您理解的一小部分特定异常。

最后,引发一个裸露的 Exception 类是不被允许的。在 python 3 中,我相信它实际上是被禁止的,所以无论如何它都是不可移植的。但即使在 Python 中,最好提供一个 Exception 实例,而不是类:

引发系统退出(1)

  1. try 块中的所有函数都使用 raise 引发异常

create_logdir() 的示例是函数定义

def create_logdir():

尝试: os.makedirs(LOG_DIR) except OSError as e: sys.stderr.write("无法创建日志目录...退出!!!") raise print "日志文件:" + Corrupt_log return True

def main(): 尝试: create_logdir() except Exception as e:logging.exception(e) raise SystemExit

(a) 如果 create_logdir() 失败,我们将收到以下错误,这可以吗还是我需要改进此代码。

无法创建日志目录...正在退出!错误:根:[Errno 17] 文件存在:'/var/log/dummy'

回溯(最近一次调用最后一次):文件“corrupt_test.py”,第 245 行,在主 create_logdir() 文件“corrupt_test.py”,第 53 行,在 create_logdir os.makedirs(LOG_DIR) 文件“/usr/local/lib/ python2.7/os.py”,第 157 行,在 makedirs OSError: [Errno 17] 文件存在:'/var/log/dummy'

我更喜欢冒泡方法,也许像您所做的那样带有日志或警告消息,例如:

logging.exception("create_logdir 失败: makedirs(%r): %s" % (LOG_DIR, e)) raise

(也不是说该日志消息记录了更多上下文:上下文在调试问题时非常有用。)

对于非常小的脚本 sys.stderr.write 是可以的,但一般来说,任何通常有用的函数都可能会迁移到库中以便重用;考虑到 stderr 并不总是存放消息的地方;相反,根据需要使用 error() 或 wanr() 或 exception() 读取日志记录模块。有更多的空间来配置输出的位置,而无需将其连接到内部函数中。

  1. 我可以直接 raise ,而不是 SystemExit 或 sys.exit(1) 吗?这对我来说看起来不对

def main():

尝试:create_logdir() except Exception as elogging.exception(e) raise

这就是我自己会做的事情。

想一想:异常是否已被“处理​​”,即情况是否因为预期而得到处理?如果没有,则让异常冒出,以便用户知道发生了程序无法理解的事情。

最后,除了最外面的 main() 函数之外,从任何内部调用 SystemExit 或 sys.exit() 通常都是不好的。即使在那里我也抗拒它;main 函数,如果写得好,通常可以从其他地方有效地调用,这使得它实际上成为一个库函数(它已被重用)。这样的函数不应该单方面中止程序。太粗鲁了!相反,让异常冒出来:也许main() 的调用者期望它并且可以处理它。通过中止而不是“引发”,您剥夺了调用者执行适当操作的机会,即使您自己(即“main”)不知道足够的上下文来处理异常。

所以我主张“提高”自己。然后只是因为您想记录错误。如果您不想记录异常,您可以完全避免 try/ except 并使用更简单的代码:让调用者担心未处理的异常!