程序退出
正常情况下Python脚本在程序末尾退出,我们也可以通过sys和os模块里的工具显示地调用程序退出。
sys模块退出
sys.exit(N)抛出一个内建的SystemExit异常,并以状态N退出。
我们可以捕捉异常以拦截过早退出:
>>> try:
... sys.exit()
... except SystemExit:
... print('ignoring exit')
...
ignoring exit
事实上,用Python的raise语句显示地抛出内建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/except和try/finally对os._exit均不起作用。
Shell命令退出状态代码
sys.exit和os._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.wait和os.waitpid调用获知。
用os.system和os.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.popen和subprocess.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 大连妇科医院哪个好 http://www.83692222.cn/
"分支子进程,用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可以在其退出后再次使用)。
在某些系统平台下如果print和input有可能发生流访问交叠的话,那么也需要同步化。
线程通常在其运行的函数返回后默默地退出,我们也可以显示地调用_thread.exit是线程终止,它和sys.exit基本上一样,都是抛出SystemExit异常。
备选的线程threading模块没有相当于_thread.exit的方法,但也可用raise SystemExit或sys.exit等达到相同的效果。
复习一下,两个线程模型的行为有所不同:在_thread中,大多数平台上的程序随其父线程的退出而退出,但在threading模块中它们通常不退出,除非子线程被设置为守护线程。而使用线程时,子进程通常比父进程存在的时间长。线程是进程内的函数调用,但进程的独立性和自主性更强一些的话。
大多是脚本是在运行完源代码的最后一行后退出的,而大多数线程函数仅仅执行返回操作;显示退出调用一般仅对于例外情况适用,而且适用情景不多。