1

Say I have a function that can produce a vast variety of errors.

I have a ValueError that I need to catch, a specific AttributeError, and then I also need to handle any other type of error.

try:
  func()
except AttributeError as e:
  if "specific error text" in str(e):
    print("The specific AttributeError occurred")
  else:
    raise
except ValueError:
  print("A value error occurred")
except Exception as e:
  print("Another error occurred: {}".format(str(e)))

Problem: If func() bubbles an AttributeError that's not the specific one I'm looking for, in this case, it'll be re-raised and not handled how I want it to be handled (via the general Exception handler).

How do I force non-specific errors to be handled further down in the chain, without duplicating code from the Exception section into the AttributeError section?

snazzybouche
  • 2,241
  • 3
  • 21
  • 51
  • is your `CustomError` a subclass of `AttributeError`? – Azat Ibrakov Apr 17 '19 at 03:37
  • The code you have there should work. Compare the string of `e` to the exception you're looking for. This should be what you're looking for: https://stackoverflow.com/questions/13531247/python-catching-specific-exception – Steven Tautonico Apr 17 '19 at 03:37
  • Yes, it works, but if `func()` creates an `AttributeError` that does not contain `"specific error text"`, then an exception is raised - the intent is that such an error would be handled by `Exception` – snazzybouche Apr 17 '19 at 03:38
  • @AzatIbrakov It is not. I'll swap it out for ValueError, to avoid further confusion – snazzybouche Apr 17 '19 at 03:45
  • can you provide a snippet of your `func` and `specific error text` example? – Azat Ibrakov Apr 17 '19 at 03:56
  • @AzatIbrakov `func()` is `getattr(COMMANDS, cmd.command).command(irc_c, msg, cmd)`, which checks a directory of IRC commands to see if the given string (`cmd.command`) is a match. If it is, it runs the `command` function of that class. If not, it elicits an `AttributeError`. `COMMANDS` is a `Command_Directory` object, and the specific text I'm checking for is `"'Commands_Directory' object has no attribute"` (which would mean that the given command is not in the directory) – snazzybouche Apr 17 '19 at 04:06

2 Answers2

2

As an option you can process AttributeError and ValueError in one try-except block and all other Exceptions on the top level like

try:
    try:
        func()
    except AttributeError as e:
        if "specific error text" in str(e):
            print("The specific AttributeError occurred")
        else:
            raise
    except ValueError:
        print("A value error occurred")
except Exception as e:
    print("Another error occurred: {}".format(str(e)))

this may look a bit ugly though, so we can extract inner try-except block in a separate function like

def func_with_expected_exceptions_handling():
    try:
        func()
    except AttributeError as e:
        if "specific error text" in str(e):
            print("The specific AttributeError occurred")
        else:
            raise
    except ValueError:
        print("A value error occurred")

and after that

try:
    func_with_expected_exceptions_handling()
except Exception as e:
    print("Another error occurred: {}".format(str(e)))

this doesn't save us from an actual nested structure, but it may come in handy if this kind of func processing arises in other places.

BTW, I don't think checking for a specific error message in exception is a good idea, we need a little bit more context to see if it can be done easier.

EDIT

If I understood correctly your func looks like

def func(...):
    getattr(COMMANDS, cmd.command).command(irc_c, msg, cmd)

and you want to handle error from getattr call.

I can see next options here:

  • Wrap getattr call in try-except and process AttributeError in-place

    def func(...):
        try:
            commander = getattr(COMMANDS, cmd.command)
        except AttributeError:
            print('Command {} not found'.format(cmd.command))
        else:
            commander.command(irc_c, msg, cmd)
    
  • Wrap getattr call in try-except, re-raise a custom exception (or ValueError) and process it afterwards in OP try-except

    class CommandNotFound(Exception): pass
    
    
    def func(...):
        try:
            commander = getattr(COMMANDS, cmd.command)
        except AttributeError:
            raise CommandNotFound()  # or we can use `ValueError` instead
        else:
            commander.command(irc_c, msg, cmd)
    
  • Use default parameter of getattr function and make some kind of logging there like

    class DefaultCommand:
        def command(self, irc_c, msg, cmd):
            print("Command {} is not found".format(cmd.command))
    

    and after that used like

    getattr(COMMANDS, cmd.command, DefaultCommand()).command(irc_c, msg, cmd)
    
Azat Ibrakov
  • 9,998
  • 9
  • 38
  • 50
  • These solutions are all really well thought-through. It's weird how seeing a try/except block makes me think of one specific way of doing things, but that limited mindset evidently does not apply to you – snazzybouche Apr 17 '19 at 13:41
1

Basically you need to handle the specific error first. From more general to more specific, i.e Exception => AttributeError => YourError

>>> try:
...     raise MyCustomAttrErr("Hey, this failed!")
... except MyCustomAttrErr as e:
...     print(e)
... except AttributteError as e:
...     print("Attribute error raised")
... except Exception as e:
...     print("Base exception raised")
... 
Hey, this failed!

Python handled the except blocks in order from top to bottom and stops in the first block that captures it.

Netwave
  • 40,134
  • 6
  • 50
  • 93
  • Thanks for your advice, but in this case `CustomError` is unrelated, I could just as easily swap that out for `ValueError` or anything else. This question relates to catching the *specific* `AttributeError`, which I'm currently detecting via analysis of `str(e)`. – snazzybouche Apr 17 '19 at 03:40
  • @snazzybouche, in that case you can define an specific instance if the error str is always gonna be the same and use the `is` operator for example. Or declare an specific exception with that message and handle it the normal way. For any other thing I think you are missusing the exception handling. – Netwave Apr 17 '19 at 03:43
  • I marked Azat's answer as correct, as it most correct for the question, but for this specific case I did end up using a `__getattr__` to raise a custom error. Thank you for your help. – snazzybouche Apr 17 '19 at 17:39