5.4. Program Exits
As we've seen, unlike C, there is no "main" function in Python. When we run a program, we simply execute all of the code in the top-level file, from top to bottom (i.e., in the filename we listed in the command line, clicked in a file explorer, and so on). Scripts normally exit when Python falls off the end of the file, but we may also call for program exit explicitly with the built-in sys.exit function:
>>> sys.exit(N) # else exits on end of script, with status N
Interestingly, this call really just raises the built-in SystemExit exception. Because of this, we can catch it as usual to intercept early exits and perform cleanup activities; if uncaught, the interpreter exits as usual. For instance:
C:\...\PP3E\System>python >>> import sys >>> try: ... sys.exit( ) # see also: os._exit, Tk().quit( ) ... except SystemExit: ... print 'ignoring exit' ... ignoring exit >>>
In fact, explicitly raising the built-in SystemExit exception with a Python raise statement is equivalent to calling sys.exit. More realistically, a try block would catch the exit exception raised elsewhere in a program; the script in Example 5-12 exits from within a processing function.
Example 5-12. PP3E\System\Exits\testexit_sys.py
Running this program as a script causes it to exit before the interpreter falls off the end of the file. But because sys.exit raises a Python exception, importers of its function can trap and override its exit exception, or specify a finally cleanup block to be run during program exit processing:
C:\...\PP3E\System\Exits>python testexit_sys.py Bye sys world C:\...\PP3E\System\Exits>python >>> from testexit_sys import later >>> try: ... later( ) ... except SystemExit: ... print 'Ignored...' ... Bye sys world Ignored... >>> try: ... later( ) ... finally: ... print 'Cleanup' ... Bye sys world Cleanup C:\...\PP3E\System\Exits>
5.4.1. os Module Exits
It's possible to exit Python in other ways, too. For instance, within a forked child process on Unix, we typically call the os._exit function rather than sys.exit, tHReads may exit with a thread.exit call, and Tkinter GUI applications often end by calling something named Tk().quit( ). We'll meet the Tkinter module later in this book, but os and tHRead exits merit a look here. When os._exit is called, the calling process exits immediately instead of raising an exception that could be trapped and ignored (see Example 5-13).
Example 5-13. PP3E\System\Exits\testexit_os.py
Unlike sys.exit, os._exit is immune to both try/except and try/finally interception:
C:\...\PP3E\System\Exits>python testexit_os.py Bye os world C:\...\PP3E\System\Exits>python >>> from testexit_os import outahere >>> try: ... outahere( ) ... except: ... print 'Ignored' ... Bye os world C:\...\PP3E\System\Exits>python >>> from testexit_os import outahere >>> try: ... outahere( ) ... finally: ... print 'Cleanup' ... Bye os world
5.4.2. Exit Status Codes
Both the sys and os exit calls we just met accept an argument that denotes the exit status code of the process (it's optional in the sys call but required by os). After exit, this code may be interrogated in shells and by programs that ran the script as a child process. On Linux, for example, we ask for the "status" shell variable's value in order to fetch the last program's exit status; by convention, a nonzero status generally indicates that some sort of problem occurred.
[mark@toy]$ python testexit_sys.py Bye sys world [mark@toy]$ echo $status 42 [mark@toy]$ python testexit_os.py Bye os world [mark@toy]$ echo $status 99
In a chain of command-line programs, exit statuses could be checked along the way as a simple form of cross-program communication. We can also grab hold of the exit status of a program run by another script. When launching shell commands, it's provided as the return value of an os.system call and the return value of the close method of an os.popen object; when forking programs, the exit status is available through the os.wait and os.waitpid calls in a parent process. Let's look at the case of the shell commands first:
[mark@toy]$ python >>> import os >>> pipe = os.popen('python testexit_sys.py') >>> pipe.read( ) 'Bye sys world\012' >>> stat = pipe.close( ) # returns exit code >>> stat 10752 >>> hex(stat) '0x2a00' >>> stat >> 8 42 >>> pipe = os.popen('python testexit_os.py') >>> stat = pipe.close( ) >>> stat, stat >> 8 (25344, 99)
When using os.popen, the exit status, for reasons we won't go into here, is actually packed into specific bit positions of the return value; it's really there, but we need to shift the result right by eight bits to see it. Commands run with os.system send their statuses back through the Python library call:
>>> import os >>> for prog in ('testexit_sys.py', 'testexit_os.py'): ... stat = os.system('python ' + prog) ... print prog, stat, stat >> 8 ... Bye sys world testexit_sys.py 10752 42 Bye os world testexit_os.py 25344 99
Unlike when I wrote the previous edition of this book, exit status works on Windows now too, though it is not encoded in a bit mask as on Linux:
>>> import sys >>> sys.platform 'win32' >>> import os >>> stat = os.system('python testexit_sys.py') Bye sys world >>> stat 42 >>> pipe = os.popen('python testexit_sys.py') >>> print pipe.read( ), Bye sys world >>> stat = pipe.close( ) >>> stat 42 >>> os.system('python testexit_os.py') Bye os world 99 >>> pipe = os.popen('python -u testexit_os.py') >>> pipe.read(); pipe.close( ) 'Bye os world\n' 99
Notice the last test in the preceding code. Here, we have to run the os exit script in unbuffered mode with the -u Python command-line flag. Otherwise, the text printed to the standard output stream will not be flushed from its buffer when os._exit is called in this case (by default, standard output is buffered). In practice, flushing buffers, if required, should probably be done in the exiting script itself. More on buffering when we discuss deadlocks later in this chapter.
>>> os.popen('python -u testexit_os.py').read( ) 'Bye os world\n' >>> os.popen('python testexit_os.py').read( ) ''
5.4.3. Process Exit Status
Now, to learn how to get the exit status from forked processes, let's write a simple forking program: the script in Example 5-14 forks child processes and prints child process exit statuses returned by os.wait calls in the parent until a "q" is typed at the console.
Example 5-14. PP3E\System\Exits\testexit_fork.py
Running this program on Linux (remember, fork still doesn't work on Windows as I write the third edition of this book) produces the following results:
[mark@toy]$ python testexit_fork.py Hello from child 723 1 Parent got 723 256 1 Hello from child 724 1 Parent got 724 256 1 Hello from child 725 1 Parent got 725 256 1 q
If you study this output closely, you'll notice that the exit status (the last number printed) is always the samethe number 1. Because forked processes begin life as copies of the process that created them, they also have copies of global memory. Because of that, each forked child gets and changes its own exitstat global variable without changing any other process's copy of this variable.
5.4.4. Thread Exits
In contrast, threads run in parallel within the same process and share global memory. Each thread in Example 5-15 changes the single shared global variable, exitstat.
Example 5-15. PP3E\System\Exits\testexit_thread.py
Here is this script in action on Linux; the global exitstat is changed by each thread, because threads share global memory within the process. In fact, this is often how threads communicate in general. Rather than exit status codes, threads assign module-level globals to signal conditions and use thread module locks and queues to synchronize access to shared globals if needed (this script normally should too if it ever does something more realistic, but for this simple demo, it forgoes locks by assuming threads won't overlap):
[mark@toy]$ /usr/bin/python testexit_thread.py Hello from child 1026 1 Hello from child 2050 2 Hello from child 3074 3 q
Unlike forks, threads now run in the standard version of Python on Windows too. This program works the same there, but thread identifiers differ; they are arbitrary but unique among active threads and so may be used as dictionary keys to keep per-thread information:
C:\...\PP3E\System\Exits>python testexit_thread.py Hello from child -587879 1 Hello from child -587879 2 Hello from child -587879 3 q
Speaking of exits, a thread normally exits silently when the function it runs returns, and the function return value is ignored. Optionally, the thread.exit function can be called to terminate the calling thread explicitly. This call works almost exactly like sys.exit (but takes no return status argument), and it works by raising a SystemExit exception in the calling thread. Because of that, a thread can also prematurely end by calling sys.exit or by directly raising SystemExit. Be sure not to call os._exit within a thread function, thoughdoing so hangs the entire process on my Linux system and kills every thread in the process on Windows!
When used well, exit status can be used to implement error detection and simple communication protocols in systems composed of command-line scripts. But having said that, I should underscore that most scripts do simply fall off the end of the source to exit, and most thread functions simply return; explicit exit calls are generally employed for exceptional conditions only.