See All Titles |
![]() ![]() *Creating ExceptionsAlthough the set of standard exceptions is fairly wide-ranging, it may be advantageous to create your own exceptions. One situation is where you would like additional information from what a standard or module-specific exception provides. We will present two examples, both related to IOError. IOError is a generic exception used for input/output problems which may arise from invalid file access or other forms of communication. Suppose we wanted to be more specific in terms of identifying the source of the problem. For example, for file errors, we want to have a FileError exception which behaves like IOError, but with a name that has more meaning when performing file operations. Another exception we will look at is related to network programming with sockets. The exception generated by the socket module is called socket.error and is not a built-in exception. It is subclassed from the generic Exception exception. However, the exception arguments from socket.error closely resemble those of IOError exceptions, so we are going to define a new exception called NetworkError which subclasses from IOError but contains at least the information provided by socket.error. Like classes and object-oriented programming, we have not formally covered network programming at this stage, but skip ahead to Chapter 16 if you need to. We now present a module called myexc.py with our newly-customized exceptions FileError and NetworkError. The code is in Example 10.3 Example 10.3. Creating Exceptions (myexc.py)This module defines two new exceptions, FileError and NetworkError, as well as reimplements more diagnostic versions of open() [myopen()]and socket.connect() [myconnect()]. Also included is a test function [ test()] that is run if this module is executed directly. <$nopage> 001 1 #!/usr/bin/env python 002 2 003 3 import os, socket, errno, types, tempfile 004 4 005 5 class NetworkError(IOError): 006 6 pass <$nopage> 007 7 008 8 class FileError(IOError): 009 9 pass <$nopage> 010 10 011 11 def updArgs(args, newarg=None): 012 12 013 13 if type(args) == types.InstanceType: 014 14 myargs = [] 015 15 for eachArg in args: 016 16 myargs.append(eachArg) 017 17 else: <$nopage> 018 18 myargs = list(args) 019 19 020 20 if newarg: 021 21 myargs.append(newarg) 022 22 023 23 return tuple(myargs) 024 24 025 25 def fileArgs(file, mode, args): 026 26 027 27 if args[0] == errno.EACCES and \ 028 28 'access' in dir(os): 029 29 perms = '' 030 30 permd = { 'r': os.R_OK, 'w': os.W_OK, 031 31 'x': os.X_OK} 032 32 pkeys = permd.keys() 033 33 pkeys.sort() 034 34 pkeys.reverse() 035 35 036 36 for eachPerm in 'rwx': 037 37 if os.access(file, permd[eachPerm]): 038 38 perms = perms + eachPerm 039 39 else: <$nopage> 040 40 perms = perms + '-' 041 41 042 42 if type(args) == types.InstanceType: 043 43 myargs = [] 044 44 for eachArg in args: 045 45 myargs.append(eachArg) 046 46 else: <$nopage> 047 47 myargs = list(args) 048 48 049 49 myargs[1] = "'%s' %s (perms: '%s')" % \ 050 50 (mode, myargs[1], perms) 051 51 052 52 myargs.append(args.filename) 053 53 054 54 else: <$nopage> 055 55 myargs = args 056 56 057 57 return tuple(myargs) 058 58 059 59 def myconnect(sock, host, port): 060 60 061 61 try: <$nopage> 062 62 sock.connect((host, port)) 063 63 064 64 except socket.error, args: 065 65 myargs = updArgs(args)# conv inst2tuple 066 66 if len(myargs) == 1:# no #s on some errs 067 67 myargs = (errno.ENXIO, myargs[0]) 068 68 069 69 raise NetworkError, \ 070 70 updArgs(myargs, host + ':' + str(port)) 071 71 072 72 def myopen(file, mode='r'): 073 73 074 74 try: <$nopage> 075 75 fo = open(file, mode) 076 76 077 77 except IOError, args: 078 78 raise FileError, fileArgs(file, mode, args) 079 79 080 80 return fo 081 81 082 82 def testfile(): 083 83 084 84 file = mktemp() 085 85 f = open(file, 'w') 086 86 f.close() 087 87 088 88 for eachTest in ((0, 'r'), (0100, 'r'), \ 089 89 0400, 'w'), (0500, 'w')): 090 90 try: <$nopage> 091 91 os.chmod(file, eachTest[0]) 092 92 f = myopen(file, eachTest[1]) 093 93 094 94 except FileError, args: 095 95 print "%s: %s" % \ 096 96 (args.__class__.__name__, args) 097 97 else: <$nopage> 098 98 print file, "opened ok… perm ignored" 099 99 f.close() 100 100 101 101 os.chmod(file, 0777)# enable all perms 102 102 os.unlink(file) 103 103 104 104 def testnet(): 105 105 s = socket.socket(socket.AF_INET, \ 106 106 socket.SOCK_STREAM) 107 107 108 108 for eachHost in ('deli', 'www'): 109 109 try: <$nopage> 110 110 myconnect(s, 'deli', 8080) 111 111 except NetworkError, args: 112 112 print "%s: %s" % \ 113 113 (args.__class__.__name__, args) 114 114 115 115 if __name__ == '__main__': 116 116 testfile() 117 117 testnet() 118 <$nopage> Lines 1 – 3The Unix start-up script and importation of the socket, os, errno, types, and tempfile modules help us start this module. Lines 5 – 9Believe it or not, these five lines make up our new exceptions. Not just one, but both of them. Unless new functionality is going to be introduced, creating a new exception is just a matter of subclassing from an already-existing exception. In our case, that would be IOError. EnvironmentError, from which IOError is derived would also work, but we wanted to convey that our exceptions were definitely I/O-related. We chose IOError because it provides two arguments, an error number and an error string. File-related [uses open()] IOError exceptions even support a third argument which is not part of the main set of exception arguments, and that would be the file name. Special handling is done for this third argument which lives outside the main tuple pair and has the name filename. Lines 11 – 23The entire purpose of the updArgs() function is to "update" the exception arguments. What we mean here is that the original exception is going to provide us a set of arguments. We want to take these arguments and make them part of our new exception, perhaps embellishing or adding a third argument (which is not added if nothing is given— None is a default argument which we will study in the next chapter). Our goal is to provide the more informative details to the user so that if and when errors occur, the problems can be tracked down as quickly as possible. Lines 25 – 57The fileArgs() function is used only by myopen() [see below]. In particular, we are seeking error EACCES, which represents "permission denied." We pass all other IOError exceptions along without modification (lines 54–55). If you are curious about ENXIO, EACCES, and other system error numbers, you can hunt them down by starting at file /usr/include/sys/errno.h on a Unix system, or C:\Msdev\include\Errno.h if you are using Visual C++ on Windows. In line 27, we are also checking to make sure that the machine we are using supports the os.access() function, which helps you check what kind of file permissions you have for any particular file. We do not proceed unless we receive both a permission error as well as the ability to check what kind of permissions we have. If all checks out, we set up a dictionary to help us build a string indicating the permissions we have on our file. The Unix file system uses explicit file permissions for the user, group (more than one user can belong to a "group"), and other (any user other than the owner or someone in the same group as the owner) in read, write, and execute ('r', 'w', 'x') order. Windows supports some of these permissions. Now it is time to build the permission string. If the file has a permission, its corresponding letter shows up in the string, otherwise a dash ( - ) appears. For example, a string of "rw-" means that you have read and write access to it. If the string reads "r-x", you have only read and execute access; "---" means no permission at all. After the permission string has been constructed, we create a temporary argument list. We then alter the error string to contain the permission string, something which standard IOError exception does not provide. "Permission denied" sometimes seems silly if the system does not tell you what permissions you have to correct the problem. The reason, of course, is security. When intruders do not have permission to access something, the last thing you want them to see is what the file permissions are, hence the dilemma. However, our example here is merely an exercise, so we allow for the temporary "breach of security." The point is to verify whether or not the os.chmod() functions call affected file permissions the way they are supposed to. The final thing we do is to add the file name to our argument list and return the set of arguments as a tuple. Lines 59 – 70Our new myconnect() function simply wraps the standard socket method connect() to provide an IOError-type exception if the network connection fails. Unlike the general socket.error exception, we also provide the host name and port number as an added value to the programmer. For those new to network programming, a host name and port number pair are analogous to an area code and telephone number when you are trying to contact someone. In this case, we are trying to contact a program running on the remote host, presumably a server of some sort; therefore, we require the host's name and the port number that the server is listening on. When a failure occurs, the error number and error string are quite helpful, but it would be even more helpful to have the exact host-port combination as well, since this pair may be dynamically-generated or retrieved from some database or name service. That is the value-add we are bestowing to our version of connect(). Another issue arises when a host cannot be found. There is no direct error number given to us by the socket.error exception, so to make it conform to the IOError protocol of providing an error number-error string pair, we find the closest error number that matches. We choose ENXIO. Lines 72 – 80Like its sibling myconnect(), myopen() also wraps around an existing piece of code. Here, we have the open() function. Our handler catches only IOError exceptions. All others will pass through and on up to the next level (when no handler is found for them). Once an IOError is caught, we raise our own error and customized arguments as returned from fileArgs(). Lines 82 – 102We shall perform the file testing first, here using the testfile() function. In order to begin, we need to create a test file that we can manipulate by changing its permissions to generate permission errors. The tempfile module contains code to create temporary file names or temporary files themselves. We just need the name for now and use our new myopen() function to create an empty file. Note that if an error occurred here, there would be no handler, and our program would terminate fatally—the test program should not continue if we cannot even create a test file. Our test uses four different permission configurations. A zero means no permissions at all, 0100 means execute-only, 0400 indicates read-only, and 0500 means read- and execute-only (0400 + 0100). In all cases, we will attempt to open a file with an invalid mode. The os.chmod() function is responsible for updating a file's permission modes. (NOTE: these permissions all have a leading zero in front, indicating that they are octal [base 8] numbers.) If an error occurs, we want to display diagnostic information similar to the way the Python interpreter performs the same task when uncaught exceptions occur, and that is giving the exception name followed by its arguments. The __class__ special variable provides the class object for which an instance was created from. Rather than displaying the entire class name here (myexc.FileError), we use the class object's __name__ variable to just display the class name (FileError), which is also what you see from the interpreter in an unhandled error situation. Then the arguments which we arduously put together in our wrapper functions follow. If the file opened successfully, that means the permissions were ignored for some reason. We indicate this with a diagnostic message and close the file. Once all tests have been completed, we enable all permissions for the file and remove it with the os.unlink() function. os.remove() is equivalent to os.unlink().) Lines 104 – 113The next section of code (testnet()) tests our NetworkError exception. A socket is a communication endpoint with which to establish contact with another host. We create such an object, then use it in an attempt to connect to a host with no server to accept our connect request and a host not on our network. Lines 115 – 117We want to execute our test*() functions only when invoking this script directly, and that is what the code here does. Most of the scripts given in this text utilize the same format. Running this on a Unix machine, we get the following output: % myexc.py FileError: [Errno 13] 'r' Permission denied (perms: '---'): '/usr/tmp/@18908.1' FileError: [Errno 13] 'r' Permission denied (perms: '--x'): '/usr/tmp/@18908.1' FileError: [Errno 13] 'w' Permission denied (perms: 'r--'): '/usr/tmp/@18908.1' FileError: [Errno 13] 'w' Permission denied (perms: 'r-x'): '/usr/tmp/@18908.1' NetworkError: [Errno 146] Connection refused: 'deli:8080' NetworkError: [Errno 6] host not found: 'www:8080' The results are slightly different on a Windows machine: D:\python>python myexc.py C:\WINDOWS\TEMP\~-195619-1 opened ok… perms ignored C:\WINDOWS\TEMP\~-195619-1 opened ok… perms ignored FileError: [Errno 13] 'w' Permission denied (perms: 'r–x'): 'C:\\WINDOWS\\TEMP\\~-195619-1' FileError: [Errno 13] 'w' Permission denied (perms: 'r–x'): 'C:\\WINDOWS\\TEMP\\~-195619-1' NetworkError: [Errno 10061] winsock error: 'deli:8080' NetworkError: [Errno 6] host not found: 'www:8080' You will notice that Windows does not support read permissions on files, which is the reason why the first two file open attempts succeeded. Your mileage may vary (YMMV) on your own machine and operating system.
|
© 2002, O'Reilly & Associates, Inc. |