"Ask forgiveness not permission" - explain [closed]
Asked Answered
Y

8

109

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 tests to see if your code is "pythonic". But to me, pythonic means clean, simple and intuitive, not loaded with exception handlers for bad coding.

So, practical example. I define a class:

class foo(object):
    bar = None

    def __init__(self):
        # a million lines of code
        self.bar = "Spike is my favorite vampire."
        # a million more lines of code

Now, coming from a procedural background, in another function I wanna do this:

if foo.bar:
    # do stuff

I'll get an attribute exception if I was impatient and did not do the initial foo = None. So, "ask forgiveness not permission" suggests I should do this instead?

try:
    if foo.bar:
        # do stuff
except:
    # this runs because my other code was sloppy?

Why would it be better for me to add additional logic in a try block just so I can leave my class definition more ambiguous? Why not define everything initially, therfore explicitly grant permission?

(Don't beat me up about using try/except blocks... I use them everywhere. I just don't think it's right to use them to catch my own errors because I wasn't a thorough programmer.)

Or... do I completely misunderstand the "Ask Forgivess" mantra?

Yapon answered 4/9, 2012 at 14:18 Comment(4)
Your statement "not loaded with exception handlers for bad coding" is a non-sequitur. Good code has all the exception handling necessary, but not one bit more. And the presence of exception handlers hardly implies "bad coding." I would think its preferable to let an application handle a problem if possible (if for no other reason than to close gracefully) rather than allow it to crash unceremoniously in the lap of the user :)Integumentary
You shouldn't try to add exception handlers to catch bad coding, especially not a bare 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.Marimaria
Another advantage of asking forgiveness is that it can sometimes avoid bugs that permission simply can't, when you're dealing with dynamic states out of your control. I think the canonical example is os.path.exists, which tells you only that the file existed or didn't exist at some point. Probably 90%+ of if os.path.exists(filename): do_something_to(filename) usages are technically buggy.Fronia
@Fronia or just open() - don't check to see if you can open the file first (permissions/existence etc...) - just try it :) [which avoids possible race conditions]Hanshansard
M
87

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.

Martyry answered 4/9, 2012 at 14:40 Comment(1)
Everyone had great comments. I marked this as the answer because it clarifies two additional things for me: "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)." is a necessary reminder to me of how things work in OO programming, and "When there are multiple potential failure modes, being prepared to get forgiveness is hugely valuable." Thanks to everyone for good replies, and sorry for my poor examples.Yapon
F
114

“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.

Furst answered 4/9, 2012 at 19:49 Comment(3)
Gilles can I ask a side question that is not relevant to the question itself but more with SO philosophy? Why has this question been marked as non-constructive? It actually was very constructive (at least for me) cause it has 2 great answers such as yours and jonathan's. ThanksDasi
@Dasi Beats me (and regarding SO philosophy, the right thing to do in this situation would be to ask on meta).Laural
Finally, after over a decade of writing Python code, I've found a good reason for EAFP—two, in fact! Thank you! I'm surprised and saddened that I haven't read this reasoning at python.org. Unfortunately, python.org also does a poor job at discouraging people from writing general except clauses, and I've had to deal with scores of difficult-to-find bugs as a result. Anyhow, I think this is the best answer.Dextroamphetamine
M
87

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.

Martyry answered 4/9, 2012 at 14:40 Comment(1)
Everyone had great comments. I marked this as the answer because it clarifies two additional things for me: "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)." is a necessary reminder to me of how things work in OO programming, and "When there are multiple potential failure modes, being prepared to get forgiveness is hugely valuable." Thanks to everyone for good replies, and sorry for my poor examples.Yapon
C
26

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.

  • When you ask for permission, you have to perform an extra operation to ask for permission every time.
  • When asking for forgiveness, you only have to perform an extra operation sometimes, i.e. when it fails.

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/

Charlesettacharleston answered 26/7, 2017 at 3:33 Comment(3)
Would there not be cases where asking for permission is the preferred option if the failure rate is high enough though? In a most cases (and most languages) having to catch an exception is many many times slower than performing a check, so surely that makes it preferable to ask for permission if the exceptional circumstances are relatively common and the permission test is fast, right?Dekeles
Sure the test is not free and the exception handling is not free. Exception handling is in contrast to many other languages less expensive. If you have a fast test and a very high proportion of instances where the test will fail, then you may find using the test will be faster. In such a case, using the python timeit module can help you to compare the different approaches to see which one is most optimal.Charlesettacharleston
Also see all the other comments about the functional benefits of using exception handling to avoid race conditions, rather then just performance.Charlesettacharleston
N
9

You're right -- the purpose of try and except are not to cover your sloppy coding. That just leads to more sloppy coding.

Exception handling should be used to handle exceptional circumstances (sloppy coding is not an exceptional circumstance). However, often, it is easy to predict which exceptional circumstances might happen. (e.g. a your program accepts user input and uses that to access a dictionary, but the user's input wasn't a key in the dictionary ...)

Nicosia answered 4/9, 2012 at 14:21 Comment(0)
N
9

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.

Neoclassic answered 4/9, 2012 at 14:28 Comment(0)
G
7

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.

Gertrude answered 5/1, 2018 at 2:19 Comment(1)
This point is important. Don't think of Ask For Forgiveness (AFF) as a style issue. There are many cases where Ask For Permission (AFP) is functionally wrong, as for race conditions as @Gertrude said above. Say that a different way: there are cases where if you use AFP, you'll end up having to handle exceptions anyway, since the pre-condition you checked may not still be true when you execute the next line. You have to handle the exception anyway, so you may as well use AFF in the first place. And don't drive yourself crazy analyzing if there could be a race condition or not, just use AFF.Alcala
F
5

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.

Fellowman answered 4/9, 2012 at 14:38 Comment(0)
A
2

Ask forgiveness not permission is meant to simplify code. It's meant for code to be written like this when there's a reasonable expectation that .bar might trigger an AttributeError.

 try:
     print foo.bar
 except AttributeError as e
     print "No Foo!" 

Your code appears to both ask permission AND forgiveness :)

The thing is, if you reasonably expect something to fail, use a try/catch. If you don't expect something to fail, and it fails anyway, well the exception that gets thrown becomes the equivalent of a failed assertion in other languages. You see where the unexpected exception is occurring and adjust your code/assumptions accordingly.

Aegeus answered 4/9, 2012 at 14:22 Comment(3)
bare except statements are generally considered to be bad practice. Only catch exceptions that you know how to handle and nothing more.Nicosia
@Nicosia I agree, I was just copy-pasting the OPs code. Added a catch for AttributeErrorAegeus
More important than simplifying code, in my opinion, is that AFNP avoids the race condition inherent in asking permission. That is, when asking permission, the condition might change from "will succeed" to "won't succeed" after it's checked but before the operation is performed. While this isn't an issue in every use case, asking-permission should raise a red flag for anyone reviewing code, and the red flag can be avoided by making AFNP a consistent practice. @Gilles 's answer goes into awesome detail on this.Bentham

© 2022 - 2024 — McMap. All rights reserved.