“Ask forgiveness, not permission” opposes two programming styles. “Ask for permission” goes like this:
if can_do_operation():
perform_operation()
else:
handle_error_case()
“Ask forgiveness” goes like this:
try:
perform_operation()
except Unable_to_perform:
handle_error_case()
This is a situation where it is expected that attempting to perform the operation might fail, and you have to handle the situation where the operation is impossible, one way or another. For example, if the operation is accessing a file, the file might not exist.
There are two main reasons why it's better to ask for forgiveness:
- In a concurrent world (in a multithreaded program, or if the operation involves objects that are external to the program such as files, other processes, network resources, etc.), the situation might change between the time when you run
can_do_operation()
and the time when you run perform_operation()
. So you'd have to handle the error anyway.
- You need to use exactly the right criteria for asking permission. If you get it wrong, you'll either be unable to perform an operation that you could perform, or have an error occur because you can't perform the operation after all. For example, if you test whether a file exists before opening it, it's possible that the file does exist, but you can't open it because you don't have permission. Conversely, maybe the file is created when you open it (for example because it comes over a network connection that is only brought up when you actually open the file, not when you only poke to see whether it's there).
What ask-forgiveness situations have in common is that you're attempting to perform an operation, and you know that the operation may fail.
When you write foo.bar
, the non-existence of bar
is not normally considered a failure of the object foo
. It's usually a programmer error: attempting to use an object in a way that it wasn't designed for. The consequence of a programmer error in Python is an unhandled exception (if you're lucky: of course, some programmer errors can't be detected automatically). So if bar
is an optional part of the object, the normal way to deal with this is to have a bar
field that's initialized to None
, and set to some other value if the optional part is present. To test whether bar
is present, write
if foo.bar is not None:
handle_optional_part(foo.bar)
else:
default_handling()
You can abbreviate if foo.bar is not None:
to if foo.bar:
only if bar
will always be true when interpreted as a boolean — if bar
could be 0, []
, {}
or any other object that has a false truth value, you need the is not None
. It's also clearer, if you're testing for an optional part (as opposed to testing between True
and False
).
At this point you may ask: why not omit the initialization of bar
when it's not there, and test its presence with hasattr
or catch it with an AttributeError
handler? Because your code only makes sense in two cases:
- the object has no
bar
field;
- the object has a
bar
field that means what you think it means.
So when writing or deciding to use the object, you need to make sure that it doesn't have a bar
field with a different meaning. If you need to use some different object that has no bar
field, that's probably not the only thing you'll need to adapt, so you'll probably want to make a derived class or encapsulate the object in another one.
except:
clause. Your unit tests should catch these problems, and there's nothing wrong with the exception spitting out a traceback and killing the whole process for this kind of error. – Marimariaos.path.exists
, which tells you only that the file existed or didn't exist at some point. Probably 90%+ ofif os.path.exists(filename): do_something_to(filename)
usages are technically buggy. – Froniaopen()
- don't check to see if you can open the file first (permissions/existence etc...) - just try it :) [which avoids possible race conditions] – Hanshansard