1

I have multiple functions, which are chained together, something like this:

def first_function(params):
    value = None
    try:
        value = some_api_call(params)
    except ValueError:
        print("some ValueError Message!")
    if value is not None:
        return value

variable = first_function(params)

def second_function(variable):
    return some_other_api_call(variable)

As you can see, there is a try/except in the first function, and similar ones are in the other functions as well. Since all of those functions (more like 3-4) depend on each other and would raise an Error, if anyone of those failed, I'd have to include an AttributeError additionally to the other errors I'd want to catch, or check if the value exists, right?

Now I was thinking, is there a more pythonic way of doing this? In another post I read, it was stated that it's bad practice to check if a variable exists - which I would need to do, if I wanted to implement checks. Maybe I could raise the errors in every function and only catch them in the last one, or something similar?

Jan
  • 157
  • 9
  • What will your `first_function` return in the good case? What in case of an error? What will happen next? – Friedrich May 12 '23 at 10:09
  • In a good case the first function would return a custom object, in the case of an Error it would return either nothing or None (I suppose `return None` would be better). The Error appears, if a wrong value was supplied as a parameter for the API call. After that, the object is used to get some values of that object . This object however has to exist, otherwise it throws an error. After that, those values are used to create a form for user inputs. Again, the values have to exist, otherwise there will be an error. Does that help? – Jan May 12 '23 at 10:15
  • "In a good case the first function would return a custom object" - are you sure about that? Read your code again. Where is `value` assigned to? What is the difference between `if value is not None: return value`(`else return None`) and `return value`? – Friedrich May 12 '23 at 10:28
  • 1
    Sorry, the code was just an example. I think I see what you mean, I edited it. The return works properly in my code, I was just wondering about the error handling part. – Jan May 12 '23 at 11:15

2 Answers2

2

Generally (not just in Python) you want to catch an exception where you can handle it. This depends entirely on the context.

If the exception is caused by a minor inconvenience (e.g. a value not contained in a dict but you want to take a default value and go on), then handle it right there. Silently return your default and everything is fine.

If the exception is caused by something that will prevent your script from doing its work, let it slip through unhandled so it will abort the process. The user will get the information that there's a problem and where it occurred.

Anything in between is fine as well. As I wrote, it depends on the context. It's a question of design and hard to answer with an abstract example.

In your code, you print an error message if your API call fails and then carry on as if nothing happened. I see this as a problem. A single printed line may go unnoticed (or even worse: be lost in a huge log file) and the user will ask themself why your script isn't working.

Now in Python, it is more common to use exceptions for program flow than in other languages. This is usually dubbed it's easier to ask for forgiveness than permission or EAFP. As opposed to look before you leap or LBYL in more traditional languages. See the answers to "Ask forgiveness not permission" - explain or a comparison on realpython.com.

Friedrich
  • 2,011
  • 2
  • 17
  • 19
  • Ah I see... thank you for the explanation! The concept of catching the exception were I can handle it makes a lot of sense, I'll definetely try to apply it! So If I would want to keep that exception in the function because I might know the cause and want to provide some context, I would reraise it and handle it somewhere more appropriate, right? – Jan May 12 '23 at 11:20
  • As I said, it depends. It's not so much about knowing the cause but about knowing what to do about it. – Friedrich May 12 '23 at 11:33
1

All you need to do is wrap all of your function calls in a single try/except block.

Let's say we have three functions - func1, func2, func3. Either of these functions could raise an exception.

try:
    v1 = func1()
    v2 = func2(v1)
    v3 = func3(v2)
except Exception as e:
    print(e)

Thus if func1() raises an exception neither func2 nor func3 will be called. Similarly, if func2 raises an exception, func3 will not be called.

If in any of funcN you want to catch an exception (maybe just to report it) then you can always re-raise it

DarkKnight
  • 19,739
  • 3
  • 6
  • 22
  • So I'd just catch the Exception in the function and re-raise it, and handle it in the main function again, right? or alternatively, handle it just in the main function? – Jan May 12 '23 at 11:16
  • @Jan The only problem with handling everything at the "high" level is that you may not be able to [easily] deduce where the problem occurred. But that may not be important anyway – DarkKnight May 12 '23 at 11:38