common mistakes in this simple program
Martin A. Brown
martin at linux-ip.net
Mon Feb 29 16:11:16 EST 2016
Greetings Ganesh,
>> You're falling into the trap of assuming that the only exception you
>> can ever get is the one that you're planning for, and then handling.
>
>Ok sure !
This point is very important, so I'll reiterate it. I hope the poor
horse lives.
>> ALL exceptions as though they were that one. Instead catch ONLY the
>> exception that you're expecting to see, and ignore everything else. Do
>> not use a bare "except:" clause, nor even "except Exception:", for
>> this. You will appreciate it later on.
>
>What option do I have now in so ensure that I loop over the For
>loop.
>Try except is ruled out ?
No, no not at all!
#1: Yes, you (probably) should be using the try-except construct.
#2: No, you should not (ever) use a bare try-except construct.
Please read below. I will take a stab at explaining the gaps of
understanding you seem to have (others have tried already, but I'll
try, as well).
I am going to give you four different functions which demonstrate
how to use exceptions. You may find it instructive to paste these
functions into an interactive Python shell and try them out, as
well.
I will explain a few different cases, which I will show below, but I
think you'll need to figure out how to apply this to your situation.
Step 1: catch a specific Exception
----------------------------------
def catchValueError(val, default=0):
'''somebody passed in a non-convertible type, return a default'''
try:
return int(val)
except ValueError:
return default
Now, let's try passing a few values into that function:
>>> catchValueError(0)
0
>>> catchValueError('-3')
-3
>>> catchValueError('Catullus')
0
The last example above is the most interesting. We fed a string to
the builtin function int() and it raised a ValueError. Our little
try-except block, however, caught the ValueError and performed our
desired action (in this case, returning a default value).
Step 2: catch a specific Exception
----------------------------------
But, what about this:
>>> catchValueError(dict())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in catchValueError
TypeError: int() argument must be a string or a number, not 'dict'
Now, you can see that there's a different problem. The exception is
different if we feed different data to the function. So, let's try
again, but maybe we catch the TypeError instead.
def catchTypeError(val, default=0):
'''somebody passed in a non-convertible type, return a default'''
try:
return int(val)
except TypeError:
return default
This seems to work--but, now, if we try our other values (which
worked before), they don't work now:
>>> catchTypeError(dict())
0
>>> catchTypeError(0)
0
>>> catchTypeError('-3')
-3
>>> catchTypeError('Catullus')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in catchTypeError
ValueError: invalid literal for int() with base 10: 'Catullus'
Step 3: catch several different classes of Exception
----------------------------------------------------
You can a single try-except block to catch multiple classes of
specific Exception (and then perform exception handling).
def catchTVError(val, default=0):
'''somebody passed in a non-convertible type, return a default'''
try:
return int(val)
except (ValueError, TypeError):
return default
In this little toy demonstration, you can see that this is probably
the desired functionality.
>>> catchTVError(dict())
0
>>> catchTVError(0)
0
>>> catchTVError(-3)
-3
>>> catchTVError('Catullus')
0
Step 4: catch many different classes of Exception
-------------------------------------------------
Now, suppose you want to deal with each possible exception in a different
manner....you can have different exception-handling behaviour for each class of
exception.
def altCatchTVError(val, default=42):
'''somebody passed in a non-convertible type, return a default'''
try:
return int(val)
except ValueError:
return abs(default)
except TypeError:
return 0 - abs(default)
As you can see, this offers a good deal more flexibility for handling specific
exceptions that you might encounter.
>>> altCatchTVError(0)
0
>>> altCatchTVError(22)
22
>>> altCatchTVError('17')
17
>>> altCatchTVError('-3')
-3
>>> altCatchTVError('str')
42
>>> altCatchTVError(dict())
-42
Interlude and recommendation
----------------------------
As you can see, there are many possible Exceptions that can be raised when you
are calling a simple builtin function, int().
Consider now what may happen when you call out to a different program; you
indicated that your run() function calls out to subprocess.Popen(). There are
many more possible errors that can occur, just a few that can come to my mind:
* locating the program on disk
* setting up the file descriptors for the child process
* fork()ing and exec()ing the program
* memory issues
* filesystem disappears (network goes away or block device failure)
Each one of these possible errors may translate to a different exception. You
have been tempted to do:
try:
run()
except:
pass
This means that, no matter what happens, you are going to try to keep
continuing, even in the face of massive failure.
To (#1) improve the safety of your program and the environments in
which it operates, to (#2) improve your defensive programming
posture and to (#3) avoid frustrating your own debugging at some
point in the future, you would be well-advised to identify which
specific exceptions you want to ignore.
As you first try to improve the resilience of your program, you may
not be certain which exceptions you want to catch and which
represent a roadblock for your progam. This is something
that usually comes with experience.
To get that experience you can define your own exception (it'll
never get raised unless you raise it, so do not worry). Then,
create your try-except block to catch only that one. As you
encounter other exception that you are certain you wish to handle,
you can do something with them:
class UnknownException(Exception):
pass
def prep_host():
"""
Prepare clustering
"""
for cmd in ["ls -al",
"touch /tmp/file1",
"mkdir /tmp/dir1"]:
try:
if not run_cmd_and_verify(cmd, timeout=3600):
logging.info("Preparing cluster failed ...")
return False
except (UnknownException,):
pass
logging.info("Preparing Cluster.....Done !!!")
return True
Now, as you develop your program and encounter new exceptions, you
can add new except clauses to the above block with appropriate
handling, or (re-)raising the caught exception.
Comments on shelling out to other programs and using exceptions
---------------------------------------------------------------
Exceptions are great for catching logic errors, type errors,
filesystem errors and all manner of other errors within Python
programs and runtime environments. You introduce a significant
complexity the moment you fork a child (calling subprocess.Popen).
It is good, though, that you are testing the return code of the
cmd that you pass to the run() function.
Final advice:
-------------
Do not use a bare try-except.
You will frustrate your own debugging and your software may end up
trying to excecute code paths (or external programs, as you are
doing right now) for which you were sanity checking.
-Martin
--
Martin A. Brown
http://linux-ip.net/
More information about the Python-list
mailing list