Steven D'Aprano said:
for app_name in settings.INSTALLED_APPS:
try:
__import__(app_name + '.management', {}, {}, [''])
except ImportError, exc:
if exc.args[0]!='No module named management':
raise
...
I want to ask a possibly related question. My impression is
that while writing and testing something like the above code one has
somehow to know that "ImportError" can be raised, so one can explicitly
define how it is to be treated. How does one find that out?
Reading the docs. Trial and error. Background knowledge about what the
function is doing. There's no perfect solution; unfortunately many
functions fail to document what exceptions they can be expected to raise.
In principle, any exception that isn't documented is a bug. You can't be
expected to recover from bugs, all you can do is report it to the
function author.
While I've always been an advocate of (in other languages) checking return
codes from things, that's been when generally there's only been a small
number of those documented, or at least they could be split into "it wored
ok", "it almost worked", "minor problem", "huge problem". So quite often
one could write some fairly generic error handling based only on rc being 0,
or <8, or <20 or something. That's not to say that within that one couldn't
record the exact rc, error messages that went with them etc and pass them on
(to the caller, say).
Python's system seems messier.
The author of a function has a choice to make:
(1) expose any exceptions that are raised by his code to the caller; or
(2) swallow those exceptions and raise one or two known exceptions.
Neither is the "right" answer in all circumstances. Sometimes you might
want to just propagate exceptions, especially if the caller is in a
position to do something about them. Sometimes the right solution is to
hide those exceptions, and just expose a single MyModuleError exception
to callers.
Sometimes neither is ideal.
Generally, you don't. If you can't recover from an exception, there's
little point in trapping it.
The reason I want to trap them is so as not to have a more naive user
suddenly be faced with an unfriendly traceback. I'd want my program to save
details of what it'd been doing, the exception type etc, do its own cleanup
of whatever it was doing, report it had a problem, and then stop in a
'clean' fashion. I can look at the details later on...
Exceptions are:
(1) You're writing some sort of library function and you want to hide the
internal errors that occur, so you might write:
def function():
try:
processing()
except (IndexError, NameError):
raise MyModuleError("bad things happened")
except (IOError, MemoryError):
raise MyModuleError("your computer is broken")
(But notice how much potentially useful information you've thrown away by
doing this. Are you sure you want to hide that?)
It seems to me that I'd generally want something like:
def function():
try:
processing()
except (IndexError, NameError):
raise MyModuleError("bad things happened")
except (IOError, MemoryError):
raise MyModuleError("your computer is broken")
except (*):
raise MyModuleError("wallop! :" + details() )
where in the last section I want to trap any exception that wasn't one of
the named/expected ones. Your
raise MyModuleError("blah")
examples suggest that an exception can have just one string of details,
presuably passed back up the line... Is that the case or can one pass a
list/tuple of symptoms?
I did see a example somewhere something like:
except (SomeError), s:
in which 's' was then set to a list of - I think - two subsidiary values
which the exception handler could look at. But I don't know if the
equivalent of 's' for any exception always has just 2 values. And I
couldn't get anything like:
except s:
or
except *, s:
or
except (), s:
etc to work meaning "match an arbitrary exception but give me access to
whatever symptoms or location-of-error or whatever" info is available.
I also find that if the function concerned actually is a function (ie
returns something if it works properly) it's easier to think (as I have in
other languages) of it returning error information the same way, so eg:
def function2():
try:
what = processing()
except (IndexError, NameError):
what = "Error: details type 1"
except (IOError, MemoryError):
what = "Error: details type 2"
except (*):
what = "Error: unexpected: " + details()
return what
so that then the caller of the function bases their next action on whether
or not the returned value starts "Error:". That way I know that control
always comes back from that function via that return, rather than either by
a return or an exception. I don't like functions/procedures with multiple
exit points (though I realise there's a difference between normal return and
exceptional circumstance returns).
Oh, it does... Thank-you.