As we’ve seen, unlike C there is no “main” function
in Python -- when we run a program, we simply execute all 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( )
# else exits on end of script
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:...PP2ESystem>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 3-11
exits from within a processing function.
Example 3-11. PP2ESystemExits estexit_sys.py
def later( ): import sys print 'Bye sys world' sys.exit(42) print 'Never reached' if __name__ == '__main__': later( )
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:...PP2ESystemExits>python testexit_sys.py
Bye sys world C:...PP2ESystemExits>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:...PP2ESystemExits>
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 instead of
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 rather than raising an exception that could
be trapped and ignored, as shown in Example 3-12.
Example 3-12. PP2ESystemExits estexit_os.py
def outahere( ): import os print 'Bye os world' os._exit(99) print 'Never reached' if __name__ == '__main__': outahere( )
Unlike sys.exit
, os._exit
is
immune to both try
/except
and
try
/finally
interception:
C:...PP2ESystemExits>python testexit_os.py
Bye os world C:...PP2ESystemExits>python
>>>from testexit_os import outahere
>>>try:
...outahere( )
...except:
...print 'Ignored'
... Bye os world C:...PP2ESystemExits>python
>>>from testexit_os import outahere
>>>try:
...outahere( )
...finally:
...print 'Cleanup'
... Bye os world
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, we ask for the “status” shell
variable’s value to fetch the last program’s exit status;
by convention a nonzero status generally indicates 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 shell commands case first:
[mark@toy]$python
>>>import os
>>>pipe = os.popen('python testexit_sys.py')
>>>pipe.read( )
'Bye sys world 12' >>>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 is actually
packed into specific bit positions of the return value, for reasons
we won’t go into here; 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
Unfortunately, neither the popen
nor
system
interfaces for fetching exit statuses
worked reliably on Windows as I wrote this. Moreover,
fork
isn’t supported at all, and
popen
in Python 1.5.2 and earlier fails in
applications that create windows (though it works in code run from
DOS console command lines, and works better in general in 2.0). On
Windows:
>>>import os
>>>stat = os.system('python testexit_sys.py')
Bye sys world >>> print stat 0 >>>pipe = os.popen('python testexit_sys.py')
>>>print pipe.read(),
Bye sys world >>>print pipe.close( )
None>>> os.fork
Traceback (innermost last): File"<stdin>", line 1, in ? AttributeError: fork
For now, you
may need to utilize Windows-specific tools to accomplish such goals
(e.g., os.spawnv
, and running a DOS
start command with os.system
;
see later in this chapter). Be sure to watch for changes on this
front, though; Python 2.0 fixes Windows popen
problems, and ActiveState, a company that created a
fork
call for Perl on Windows, has begun focusing
on Python tools development.
To learn how to get the exit status from forked processes,
let’s write a simple forking program: the script in Example 3-13 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 3-13. PP2ESystemExits estexit_fork.py
############################################################ # fork child processes to watch exit status with os.wait; # fork works on Linux but not Windows as of Python 1.5.2; # note: spawned threads share globals, but each forked # process has its own copy of them--exitstat always the # same here but will vary if we start threads instead; ############################################################ import os exitstat = 0 def child( ): # could os.exit a script here global exitstat # change this process's global exitstat = exitstat + 1 # exit status to parent's wait print 'Hello from child', os.getpid( ), exitstat os._exit(exitstat) print 'never reached' def parent( ): while 1: newpid = os.fork( ) # start a new copy of process if newpid == 0: # if in copy, run child logic child( ) # loop until 'q' console input else: pid, status = os.wait( ) print 'Parent got', pid, status, (status >> 8) if raw_input( ) == 'q': break parent( )
Running this program on Linux (remember, fork
also
didn’t work on Windows as I wrote the second 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 1q
If you study this output closely, you’ll notice that the exit
status (the last number printed) is always the same -- the 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.
In contrast, threads run in parallel within the
same process and share global memory. Each
thread in Example 3-14 changes the single shared
global variable exitstat
.
Example 3-14. PP2ESystemExits estexit_thread.py
############################################################ # spawn threads to watch shared global memory change; # threads normally exit when the function they run returns, # but thread.exit( ) can be called to exit calling thread; # thread.exit is the same as sys.exit and raising SystemExit; # threads communicate with possibly locked global vars; ############################################################ import thread exitstat = 0 def child( ): global exitstat # process global names exitstat = exitstat + 1 # shared by all threads threadid = thread.get_ident( ) print 'Hello from child', threadid, exitstat thread.exit( ) print 'never reached' def parent( ): while 1: thread.start_new_thread(child, ( )) if raw_input( ) == 'q': break parent( )
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 to
synchronize access to shared globals if needed):
[mark@toy]$/usr/bin/python testexit_thread.py
Hello from child 1026 1 Hello from child 2050 2 Hello from child 3074 3q
Unlike forks, threads run on Windows today 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:...PP2ESystemExits>python testexit_thread.py
Hello from child -587879 1 Hello from child -587879 2 Hello from child -587879 3q
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 works by raising a SystemExit
exception in the calling thread. Because of that, a thread can also
prematurely end by calling sys.exit
, or directly
raising SystemExit
. Be sure to not call
os._exit
within a thread function,
though -- doing 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.
3.134.253.205