2

Is there a way to catch AttributeError on a specific attribute only ?

e.g. I want this code to pass if foo doesn't have a bar attribute but AttributeError exception to be thrown if it doesn't have baz an attribute.

try:
   a = foo.bar + foo.baz
except AttributeError:
    pass

I could write

if hasattr(foo, 'bar'):
    a = foo.bar + foo.baz

But I'd rather ask forgiveness than permission...

cdrom
  • 591
  • 2
  • 16
  • `foo.bar = ...` will create the attribute `bar` if it doesn't already exist. The only error you can get is if `foo` somehow disallows attributes to be created after initialization. – chepner Feb 09 '20 at 16:19
  • In other words, this already does exactly what you are asking it to do. – chepner Feb 09 '20 at 16:20
  • Your alternative implies you don't want an `AttributeError` for a non-existing `bar` attribute, but some other error if `bar` *already* exists. – chepner Feb 09 '20 at 16:21
  • How about raising an error for the inverse: `if not(hasattr(foo, 'bar')): raise AttributeError`? – Jongware Feb 09 '20 at 16:23
  • @chepner Ok, my example is bad. Let me please edit it so it makes more sense. – cdrom Feb 09 '20 at 17:35
  • 1
    @usr2564301 Using a condition before executing the code is *asking for permission*... – cdrom Feb 09 '20 at 17:41
  • 1
    If you have control over `type(foo)`, you could override `__getattr__` to raise a different exception (though still a subclass of `AttributeError`) for `bar`, and only catch that exception, rather than all `AttributeError`s. – chepner Feb 09 '20 at 18:08

2 Answers2

1

First of all, like all the coding zen statements, the forgiveness vs permission principle is not an absolute one. In this case in particular you know for sure that the first that will happen inside the try/except block is the raise of the AttributeError, but you might find yourself in situations where the AttributeError is raised after additional operations that are costly or have side effects.

def some_function(foo):
    costly_operation()
    insert_something_to_a_database(foo)
    return foo.bar + foo.baz

try:
    some_function(foo)
except AttributeError:
    # All the work done was not needed and it may even have
    # left traces (like the db insert) that now need to be undone.

In these cases, it is simpler, safer more readable and more efficient to check for permission before executing anything rather than performing unneeded operations or even trying to fix the mess afterwards.

If, anyways, you really need to ask for forgiveness rather than permission you can simply check for permission afterwards: You go ahead assuming that everything is right and, if there is an error, you figure out if it was the one that you want to raise or not.

try:
   some_function(foo)
except AttributeError:
    if not hasattr(foo, 'bar'):
        raise
Carles Sala
  • 1,989
  • 1
  • 16
  • 34
1

Move the attribute access whose exception you don't want to catch out of the try block.

x = foo.baz
try:
    a = foo.bar + x
except AttributeError:
    pass
chepner
  • 497,756
  • 71
  • 530
  • 681