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:...PP3ESystem>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. PP3ESystemExits 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:...PP3ESystemExits>python testexit_sys.py
Bye sys world C:...PP3ESystemExits>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:...PP3ESystemExits>
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. PP3ESystemExits 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:...PP3ESystemExits>python testexit_os.py
Bye os world C:...PP3ESystemExits>python
>>>from testexit_os import outahere
>>>try:
...outahere( )
...except:
...print 'Ignored'
... Bye os world C:...PP3ESystemExits>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, 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 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, 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 ' 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 ' >>>os.popen('python testexit_os.py').read( )
''
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. PP3ESystemExits 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
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 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 5-15
changes the single shared global variable, exitstat
.
Example 5-15. PP3ESystemExits 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 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 3q
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:...PP3ESystemExits>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 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, 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.145.186.83