I have two different options in mind:
def foo():
try:
# do something interesting
except:
# report error here
return bar
bar = foo()
or
def foo():
# do something
return bar
try:
bar = foo()
except:
# report error here
Is there a preferred way to do this? If so, is there a specific reason or is it simply a matter of convention? I'm curious if I'm missing something obvious here.
Here is a real-world example if generic version is confusing:
my_dict = {}
def fetch_value(key):
try:
return my_dict[key] #This would throw a KeyError
except KeyError:
print("problem")
val = fetch_value('my_key')
vs
my_dict = {}
def fetch_value(key):
return my_dict[key]
try:
val = fetch_value('my_key') #This would throw a KeyError
except KeyError:
print("problem")
3 Answers 3
Neither of these is more Pythonic than the other. The examples are too trivial to say which is preferrable but it really all depends on how things should work.
Catch and logging/reporting an issue is just a hair's breadth away from squashing exceptions which is almost always a terrible idea. The only reason I can see doing this is that you want whatever the issue is to not stop execution. If you are going to do something like this, it's really crucial to make sure that you return something sensible that works for the caller. If the next thing that happens is that the calling code throws its own exception because e.g., None
doesn't have an add
method, you are at best just making things harder to troubleshoot. It could be a lot worse, however. A lot of serious bugs are due to returning nulls/None after catching an error. I think there are times that is makes sense to do this, but they are rare in my experience.
Allowing the raw exception to bubble out is the next least-worst option, IMO. This can be fine if you are building something small where it will be easy to find the what the problem is when things crash with a KeyError
. In a situation where you are leveraging a lot of duck-typing, passing around function references, or using annotations, it can sometimes be difficult. For example, if you are using this code behind a web endpoint, what HTTP error code should you use when you catch a KeyError
. 500 might be the right answer in most cases but there might be times you want to produce something else depending on where the key was not found.
That brings me to the last option which you don't mention: catch and raise a separate, more meaningful error. That allows you to distinguish between say, a KeyError
thrown because the request was for something that isn't valid and a KeyError
thrown because of a bad configuration.
Neither is pythonic. Pythonic code would be:
my_dict = {}
def fetch_value(key):
return my_dict[key]
val = fetch_value('my_key')
Remember, simple is better than complex and flat is better than nested. Since your except
-block does not handle the exception in any meaningful way, it is better to just let it bubble up the call stack and terminate the program.
But in your code the error is ignored and it implicitly returns None
if the key is not found. If this is what you want, then it can be done simpler with the get() method:
def fetch_value(key):
return my_dict.get(key)
"Handling" an error by just logging a message and then continuing as if nothing happened, is a terrible antipattern from the Java world which has no place in Python. Exceptions should only be caught if they can be meaningfully handled.
This has nothing to do with being "Pythonic" (or not) - it is not a question of idioms and conventions, but of requirements and context.
Whether a function includes their own exception handling, or lets the caller handle its exception, depends solely on how the designer of the function wants the function to be used and reused:
if the function shall be called in different contexts, with the option of letting different callers handle errors differently, use option 2 ("exception handling outside")
if the function shall always handle its error in the very same way, regardless from where it is called, use option 1 ("exception handling inside").
And in case you need both, a function which often contains its own error handling, but not always, you are free to implement two functions - one "inner function" foo
with no own exception handling, and one outer function foo2
which wraps the call to foo
into some standard error handling.
-
stackoverflow.com/questions/25011078/what-does-pythonic-meanJimmyJames– JimmyJames2023年11月01日 18:21:10 +00:00Commented Nov 1, 2023 at 18:21
-
I agree it is vague. It is a thing, though. I thought maybe you just hadn't seen the term. That seems silly now, though. I'm a little under the weather.JimmyJames– JimmyJames2023年11月01日 18:26:13 +00:00Commented Nov 1, 2023 at 18:26
-
@candied_orange: doesn't matter, whaever Pythonic means in detail for you or me, it is not a suitable decision criterion for this question.Doc Brown– Doc Brown2023年11月01日 21:16:32 +00:00Commented Nov 1, 2023 at 21:16
fetch_value
. A lot of this decision comes down to whether you want every caller offetch_value
to throw an exception or not, but as you've got only one caller that becomes irrelevant.fetch_value()
example might not work as you expect. You're still assigning a value toval
, but it will silently beNone
. Pylint would have warned you about the invisible return.