文章目录

  • 程序退出
  • `sys`模块退出
  • `os`模块退出
  • *Shell*命令退出状态代码
  • 用`os.system`和`os.popen`获得退出状态
  • 输出流缓冲:初次介绍
  • 用`subprocess`获得退出状态
  • 进程的退出状态和共享状态
  • 线程的退出状态和共享状态


程序退出

正常情况下Python脚本在程序末尾退出,我们也可以通过sysos模块里的工具显示地调用程序退出。

sys模块退出

sys.exit(N)抛出一个内建的SystemExit异常,并以状态N退出。

我们可以捕捉异常以拦截过早退出:

>>> try:
...     sys.exit()
... except SystemExit:
...     print('ignoring exit')
... 
ignoring exit

事实上,用Pythonraise语句显示地抛出内建SystemExit异常和调用sys.exit()效果是一样的。实践中更有用的是try代码块捕捉程序其他部分抛出的退出异常。

示例:test_exit_sys.py

#!/usr/bin/env python
"测试sys.exit"

import sys


def later():
	print('Bye sys world')
	sys.exit(11)
	print('Never reached')


if __name__ == '__main__':
	later()

输出:test_exit_sys.py

Bye sys world

载入later函数的程序可以将其退出异常捕获并重写,或者编写一个负责清理的finally代码块:

>>> from test_exit_sys import later
>>> 
>>> try:
...     later()
... except SystemExit:
...     print('Ignoring...')
... 
Bye sys world
Ignoring...
>>> 
>>> try:
...     later()
... finally:
...     print('Clean up')
... 
Bye sys world
Clean up
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/python-programming---markdown-notes/my_PP4E/system$  # 交互对话进程退出

os模块退出

Unix下的分支进程中,我们通常调用os_exit函数退出。

对于os_exit,调用进程立即退出,而不是抛出可以捕获或忽略的异常。事实上,进程退出时也不输出流缓冲和运行清理处理器,所以这种做法一般应当只在分支出的子进程上进行,而最好不要用于整个程序的退出行为。

示例:test_exit_os.py

#!/usr/bin/env python
"测试os._exit"

import os


def out_here():
	print('Bye os world')
	os._exit(11)
	print('Never reach')


if __name__ == '__main__':
	out_here()

输出:test_exit_os.py

beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/python-programming---markdown-notes/my_PP4E/system$ ./test_exit_os.py 
Bye os world

sys.exit不同,try/excepttry/finallyos._exit均不起作用。

Shell命令退出状态代码

sys.exitos._exit都接受退出状态代码作为参数(在sys模块调用中为可选,但在os模块调用中为必需)。

Linux下,我们询问status这个shell变量以获得上一个程序的退出状态,通常约定以非零的数值表示出现了某种问题:

beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/python-programming---markdown-notes/my_PP4E/system$ ./test_exit_sys.py 
Bye sys world
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/python-programming---markdown-notes/my_PP4E/system$ echo $status
11
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/python-programming---markdown-notes/my_PP4E/system$ ./test_exit_os.py 
Bye os world
beacherhou@alone-Vostro-14-5401:/media/beacherhou/Coding/code_obsidian_知识库/python-programming---markdown-notes/my_PP4E/system$ echo $status
11

在启动Shell命令时,可以这样提供退出状态:

  • os.system调用的返回值
  • os.popen对象的close方法的返回值(由于历史原因,如果退出状态是0则返回None
  • subprocess模块中的多种接口(如果call函数的返回值,Popen对象的returncode属性和wait方法的结果)

通过分支进程运行程序时,退出状态可在父进程中通过os.waitos.waitpid调用获知。

os.systemos.popen获得退出状态

下面的例子在Linux上运行:

>>> pipe = os.popen('python test_exit_sys.py')
>>> pipe.read()
'Bye sys world\n'
>>> stat = pipe.close()  # 返回退出状态代码
>>> 
>>> stat
2816
>>> hex(stat)
'0xb00'
>>> stat >> 8  # 在类Unix系统下从位掩码中提取出退出状态
11
  • 在这种类Unix平台上使用os.popen,退出状态实际上被包装进返回值的特定比特位置;它的确在那里,但我们需要将结果右移8比特才能读出它。

os.system执行的命令直接通过Python库发回状态信息:

>>> os.system('./test_exit_sys.py')
Bye sys world
2816
>>> stat = os.system('./test_exit_sys.py')
Bye sys world
>>> stat
2816
>>> stat, hex(stat), stat >> 8
(2816, '0xb00', 11)

输出流缓冲:初次介绍

如果需要输出流无缓冲,可以使用-uPython命令行标识符运行目标脚本,或者使用sys.stdout.flush更改脚本以手动将内部缓冲区中的数据立刻写入文件。不然,调用os._exit立刻关闭时打印到标准输出流中的文本可能没从缓冲里冲洗出去。在默认模式下,标准输出流在连接到popen类的管道时是全缓冲的;如果连接到终端时,则仅进行行缓冲。

>>> import os
>>> 
>>> pipe = os.popen('./test_exit_os.py')
>>> pipe.read()
''
>>> 
>>> pipe = os.popen('python -u test_exit_os.py')
>>> pipe.read()
'Bye os world\n'

你可以在os.popensubprocess.Popen中传入模式和缓存参数以指定行缓冲,不过在这个示例中却没有用,因为传入这些工具的参数属于调用进程的管道输入端,而不属于派生进程的输出流:

>>> pipe = os.popen('./test_exit_os.py', 'r', 1)
>>> pipe.read()
''
>>> 
>>> from subprocess import Popen, PIPE
>>> 
>>> pipe = Popen('./test_exit_os.py', shell=True, bufsize=1, stdout=PIPE)
>>> pipe.stdout.read()
b''

subprocess获得退出状态

>>> from subprocess import Popen, PIPE, call
>>> 
>>> pipe = Popen('./test_exit_sys.py', shell=True, stdout=PIPE)
>>> pipe.stdout.read()
b'Bye sys world\n'
>>> pipe.wait()
11
>>> 
>>> call('./test_exit_sys.py', shell=True)
Bye sys world
11
>>> 
>>> pipe = Popen('./test_exit_sys.py', shell=True, stdout=PIPE)
>>> pipe.communicate()
(b'Bye sys world\n', None)
>>> pipe.returncode
11
  • 在类Unix平台下,与os.popen不同的是,它的退出状态没有被编码。

进程的退出状态和共享状态

示例:test_exit_fork.py

#!/usr/bin/env python
"分支子进程,用os.wait观察其退出状态"

import os

EXIT_STAT_INT = 0


def child():
	"子进程"
	global EXIT_STAT_INT
	EXIT_STAT_INT += 1
	print('Hello from child', os.getpid(), EXIT_STAT_INT)
	os._exit(EXIT_STAT_INT)


def main():
	"父进程"
	while True:
		new_pid_int = os.fork()

		if new_pid_int == 0:
			child()
		else:
			pid_int, status_int = os.wait()
			print('Parent got', pid_int, status_int, status_int >> 8)
			if input() == 'q':
				break


if __name__ == '__main__':
	main()

输出:test_exit_fork.py

Hello from child 18469 1
Parent got 18469 256 1

Hello from child 18475 1
Parent got 18475 256 1

Hello from child 18476 1
Parent got 18476 256 1
q

线程的退出状态和共享状态

示例:test_exit_thread.py

#!/usr/bin/env python
"派生子线程,查看其返回状态和共享状态"

import _thread as thread

EXIT_STAT_INT = 0


def child():
	"子线程"
	global EXIT_STAT_INT
	EXIT_STAT_INT += 1
	thread_id_int = thread.get_ident()
	print('Hello from child', thread_id_int, EXIT_STAT_INT)
	thread.exit()
	print('Never reach')


def main():
	while True:
		thread.start_new_thread(child, ())
		if input() == 'q':
			break


if __name__ == '__main__':
	main()

输出:test_exit_thread.py

Hello from child 140330165782080 1

Hello from child 140330165782080 2

Hello from child 140330165782080 3
q
  • 这个显示的是在Ubuntu下运行的结果。
  • Python每次创建的线程标识符都不一样。因为它们是随机生成的,但在所有运行着的活动线程中具有唯一性,因此可作为字典键以保存每个线程的信息(在某些平台上线程的id可以在其退出后再次使用)。
  • 在某些系统平台下如果printinput有可能发生流访问交叠的话,那么也需要同步化。
  • 线程通常在其运行的函数返回后默默地退出,我们也可以显示地调用_thread.exit是线程终止,它和sys.exit基本上一样,都是抛出SystemExit异常。
  • 备选的线程threading模块没有相当于_thread.exit的方法,但也可用raise SystemExitsys.exit等达到相同的效果。

复习一下,两个线程模型的行为有所不同:在_thread中,大多数平台上的程序随其父线程的退出而退出,但在threading模块中它们通常不退出,除非子线程被设置为守护线程。而使用线程时,子进程通常比父进程存在的时间长。线程是进程内的函数调用,但进程的独立性和自主性更强一些的话。

大多是脚本是在运行完源代码的最后一行后退出的,而大多数线程函数仅仅执行返回操作;显示退出调用一般仅对于例外情况适用,而且适用情景不多。