I\'m not asking for personal \"religious\" opinions about this philosophy, rather something a bit more technical.
I understand this phrase is one of several litmus t
There's lots of good answers here, I just thought I would add a point I have not seen mentioned so far.
Often asking for forgiveness instead of permission has performance improvements.
Usually the failure cases are rare, which means if you are only asking for permission, then you hardly ever have to do any extra operations. Yes, when it fails it throws an exception, as well as performing an extra operation, but exceptions in python are very fast. You can see some timings here: https://jeffknupp.com/blog/2013/02/06/write-cleaner-python-use-exceptions/
My personal non-religious opinion is that said mantra applies mainly to documented and well understood exit conditions and edge cases (e.g. I/O errors), and should never be used as a get-out-of-jail card for sloppy programming.
That said, try/except
is often used when "better" alternatives exist. For example:
# Do this
value = my_dict.get("key", None)
# instead of this
try:
value = my_dict["key"]
except KeyError:
value = None
As for your example, do use if hasattr(foo, "bar")
if your have no control over foo
and need to check conformance with your expectations, otherwise simply use foo.bar
and let the resulting error be your guide to identifying and fixing sloppy code.
In the Python context "Ask forgiveness not permission" implies a style of programming where you don't check for things to be as you expect beforhand, but rather you handle the errors that result if they are not. The classical example is not to check that a dictionary contains a given key as in:
d = {}
k = "k"
if k in d.keys():
print d[k]
else:
print "key \"" + k + "\" missing"
But rather to handle the resulting error if the key is missing:
d = {}
k = "k"
try:
print d[k]
except KeyError:
print "key \"" + k + "\" missing"
However the point is not to replace every if
in your code with a try
/except
; that would make your code decidedly messier. Instead you should only catch errors where you really can do something about them. Ideally this would reduce the amount of overall error handling in your code, making its actual purpose more evident.
While there's already a number of high quality answers, most primarily discuss this from a stylistic stand point, as appose to a functional one.
There are certain cases where we need to ask forgiveness, not permission to insure correct code (outside of a multithreaded programs).
A canonical example being,
if file_exists:
open_it()
In this example, the file could have been deleted between the check and trying to actually open the file. This is avoided by using try
:
try:
open_it()
except FileNotFoundException:
pass # file doesn't exist
This crops up in a variety of places, often working with filesystems or external APIs.
The classical "ask forgiveness not permission" example is accessing values from a dict
that may not exist. E.g.:
names = { 'joe': 'Joe Nathan', 'jo': 'Jo Mama', 'joy': 'Joy Full' }
name = 'hikaru'
try:
print names[name]
except KeyError:
print "Sorry, don't know this '{}' person".format(name)
Here the exception that might occur (KeyError
) is stated, so that you're not asking forgiveness for every error that might occur, but only the one that would naturally occur.
For comparison, the "ask permission first" approach might look like:
if name in names:
print names[name]
else:
print "Sorry, don't know this '{}' person".format(name)
or
real_name = names.get(name, None)
if real_name:
print real_name
else:
print "Sorry, don't know this '{}' person".format(name)
Such examples of "ask forgiveness" are often too simple. IMO it's not crystal clear that try
/except
blocks are inherently better than if
/else
. The real value is much clearer when performing operations that might fail in a variety of ways--such as parsing; using eval()
; accessing operating system, middleware, database, or network resources; or performing complex mathematics. When there are multiple potential failure modes, being prepared to get forgiveness is hugely valuable.
Other notes about your code examples:
You do not need to ladle try
/except
blocks around every variable usage. That would be horrible. And you don't need to set self.bar
in your __init__()
since it's set in your class
definition above. It is usual to define it either in the class (if it's data likely to be shared among all instances of the class) or in the __init__()
(if it's instance data, specific to each instance).
A value of None
is not undefined, or an error, by the way. It's a specific and legitimate value, meaning none, nil, null, or nothing. Many languages have such values so programmers don't "overload" 0
, -1
, ''
(empty string) or similar useful values.
“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:
can_do_operation()
and the time when you run perform_operation()
. So you'd have to handle the error anyway.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:
bar
field;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.