Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Raise exception vs. return None in functions? [duplicate]

What's better practice in a user-defined function in Python: raise an exception or return None? For example, I have a function that finds the most recent file in a folder.

def latestpdf(folder):
    # list the files and sort them
    try:
        latest = files[-1]
    except IndexError:
        # Folder is empty.
        return None  # One possibility
        raise FileNotFoundError()  # Alternative
    else:
        return somefunc(latest)  # In my case, somefunc parses the filename

Another option is leave the exception and handle it in the caller code, but I figure it's more clear to deal with a FileNotFoundError than an IndexError. Or is it bad form to re-raise an exception with a different name?

like image 316
parcydarks Avatar asked Aug 21 '09 19:08

parcydarks


People also ask

Does raising an error return it?

Raising an exception terminates the flow of your program, allowing the exception to bubble up the call stack. In the above example, this would let you explicitly handle TypeError later. If TypeError goes unhandled, code execution stops and you'll get an unhandled exception message.

When should you raise exceptions?

Exceptions should be used for exceptional situations outside of the normal logic of a program. In the example program an out of range value is likely to be fairly common and should be dealt with using normal if-else type logic. (See the programming exercises.)

What are the problems if the exception is raised?

When an exception is raised, no further statements in the current block of code are executed. Unless the exception is handled (described below), the interpreter will return directly to the interactive read-eval-print loop, or terminate entirely if Python was started with a file argument.

Does increasing exception stop execution?

except block is completed and the program will proceed. However, if an exception is raised in the try clause, Python will stop executing any more code in that clause, and pass the exception to the except clause to see if this particular error is handled there.


4 Answers

It's really a matter of semantics. What does foo = latestpdf(d) mean?

Is it perfectly reasonable that there's no latest file? Then sure, just return None.

Are you expecting to always find a latest file? Raise an exception. And yes, re-raising a more appropriate exception is fine.

If this is just a general function that's supposed to apply to any directory, I'd do the former and return None. If the directory is, e.g., meant to be a specific data directory that contains an application's known set of files, I'd raise an exception.

like image 177
Eevee Avatar answered Oct 08 '22 22:10

Eevee


I would make a couple suggestions before answering your question as it may answer the question for you.

  • Always name your functions descriptive. latestpdf means very little to anyone but looking over your function latestpdf() gets the latest pdf. I would suggest that you name it getLatestPdfFromFolder(folder).

As soon as I did this it became clear what it should return.. If there isn't a pdf raise an exception. But wait there more..

  • Keep the functions clearly defined. Since it's not apparent what somefuc is supposed to do and it's not (apparently) obvious how it relates to getting the latest pdf I would suggest you move it out. This makes the code much more readable.

for folder in folders:    try:        latest = getLatestPdfFromFolder(folder)        results = somefuc(latest)    except IOError: pass 

Hope this helps!

like image 27
rh0dium Avatar answered Oct 08 '22 22:10

rh0dium


I usually prefer to handle exceptions internally (i.e. try/except inside the called function, possibly returning a None) because python is dynamically typed. In general, I consider it a judgment call one way or the other, but in a dynamically typed language, there are small factors that tip the scales in favor of not passing the exception to the caller:

  1. Anyone calling your function is not notified of the exceptions that can be thrown. It becomes a bit of an art form to know what kind of exception you are hunting for (and generic except blocks ought to be avoided).
  2. if val is None is a little easier than except ComplicatedCustomExceptionThatHadToBeImportedFromSomeNameSpace. Seriously, I hate having to remember to type from django.core.exceptions import ObjectDoesNotExist at the top of all my django files just to handle a really common use case. In a statically typed world, let the editor do it for you.

Honestly, though, it's always a judgment call, and the situation you're describing, where the called function receives an error it can't help, is an excellent reason to re-raise an exception that is meaningful. You have the exact right idea, but unless you're exception is going to provide more meaningful information in a stack trace than

AttributeError: 'NoneType' object has no attribute 'foo'

which, nine times out of ten, is what the caller will see if you return an unhandled None, don't bother.

(All this kind of makes me wish that python exceptions had the cause attributes by default, as in java, which lets you pass exceptions into new exceptions so that you can rethrow all you want and never lose the original source of the problem.)

like image 21
David Berger Avatar answered Oct 08 '22 21:10

David Berger


with python 3.5's typing:

example function when returning None will be:

def latestpdf(folder: str) -> Union[str, None]

and when raising an exception will be:

def latestpdf(folder: str) -> str 

option 2 seem more readable and pythonic

(+option to add comment to exception as stated earlier.)

like image 30
Asaf Avatar answered Oct 08 '22 21:10

Asaf