1

The following is an issue I've observed in Python 3.8. Let's say I define a simple class:

class A:
    @property
    def b(self):
        return self.c # should throw AttributeError

Accessing A().b fails as expected (see below), but here's my issue:

>>> getattr(A(), 'b', 5)
5

Which essentially tells me that getattr() is the equivalent of:

def my_getattr_b(obj, default):
    try:
        return obj.b
    except AttributeError:
        # catches ANY missing attribute, not just b!
        return default

This makes me very nervous to use getattr in my code, because it can lead to completely incorrect behavior on a buggy class if it "fails" silently. The "correct" behavior ought to be that the exception is re-raised. Is the only safe solution the following?

def safe_getattr(obj, attr, default):
    if attr in dir(obj):
       # this will properly raise an AttributeError if something besides attr is missing
       return getattr(obj, attr) 
    else:
       return default

This works as expected:

>>> A().b
[...]
AttributeError: 'A' object has no attribute 'c'

This does too:

>>> getattr(A(), 'b')
[...]
AttributeError: 'A' object has no attribute 'c'
snarayanan
  • 21
  • 1
  • 1
    Maybe *don't pass a default?* – juanpa.arrivillaga Aug 30 '21 at 22:02
  • @juanpa.arrivillaga It looks like reason he's using `getattr()` is so he can specify a default. – Barmar Aug 30 '21 at 22:04
  • 1
    The problem is that he wants a default for the `b` attribute, but the getter is getting an error for a different attribute, and this is being suppressed. – Barmar Aug 30 '21 at 22:05
  • 2
    It's even worse than this example suggests. If the `b` getter gets an `AttributeError` on some completely unrelated object, it will still return the default. The documentation at https://docs.python.org/3/library/functions.html#getattr doesn't suggest any of this behavior. – Barmar Aug 30 '21 at 22:09
  • 2
    Because the alternative would be substantially more complicated - what do you do, parse it out of the error message? What about custom implementations that don't have the default structure to the message? As far as the public API is concerned that attribute _is_ missing - when you try to access it, you get an AttributeError. – jonrsharpe Aug 30 '21 at 22:16
  • 1
    @Barmar sorry I wasn't clear on that point - that's *exactly* the issue I''m concerned about. Outside of this toy example, I came across this problem when a `getattr()` was obfuscating an unrelated AttributeError inside a property. The default value returned by `getattr` allowed the program to continue without crashing immediately, leading to a very hard-to-trace issue – snarayanan Aug 30 '21 at 22:17
  • @jonrsharpe - I suppose one pure-python alternative is what I have above as `safe_getattr()`, but it admittedly doesn't cover cases where e.g. `__getattr__()` is overridden. But I think @Barmar hit my problem on the head - the docs give no hint this is the expected behavior. – snarayanan Aug 30 '21 at 22:19
  • 2
    That's what it means to be a missing attribute in Python - you get an AttributeError when you try to access it. In a language with so much dynamic behaviour that's the only really meaningful definition. `dir`'s docs explicitly call out the shortcomings that are the reason your "safe" implementation isn't in use. – jonrsharpe Aug 30 '21 at 22:24
  • 1
    @snarayanan I think you've hit the underlying problem on the head. Because of `__getattr__()` and `__get()__`, `getattr()` can't tell whether the attribute really exists or not. All it knows is whether the exception was raised. – Barmar Aug 30 '21 at 22:24
  • 1
    The issue of defining `safe_getattr` using `dir`, of course, is that classes and metaclasses can define custom `__dir__` methods... Quite difficult to think of a truly safe implementation that isn't hideously complex – Alex Waygood Aug 30 '21 at 22:31
  • 2
    Note this was reported and rejected: https://bugs.python.org/issue39865 – jonrsharpe Aug 30 '21 at 22:32
  • 1
    Understood - the `dir` example was meant to be illustrative. It's definitely not a perfect (or even better) general solution. I guess at this point I'm satisfied that this the sanest implementation given the options, but I still think the docs ought to be far more explicit about this behavior. – snarayanan Aug 30 '21 at 22:33
  • 1
    @snarayanan, on that I agree with you! – Alex Waygood Aug 30 '21 at 22:36
  • I think the fundamental issue is as @jonrsharpe pointed out, as far as Python is concerned, if `some_object.some_attribute` raises an `AttributeError` for *any reason*, then that attribute *does not exist* – juanpa.arrivillaga Aug 30 '21 at 22:52

0 Answers0