aosabook/500lines · error · AttributeError

{name}

Error message

{name}

What it means

Raised inside a Python (native) __getattr__ demonstration in the objmodel chapter. The example class A defines __getattr__ only to synthesise 'fahrenheit' from 'celsius'; for every other name it re-raises AttributeError(name). This is the idiomatic Python pattern: __getattr__ must raise AttributeError for names it does not handle so that hasattr and the attribute protocol behave correctly.

Source

Thrown at objmodel/objmodel.markdown:588

The case of ``__setattr__`` is a bit different. Since setting an attribute
always creates it, \newline ``__setattr__`` is always called when setting an
attribute.  To make sure that a ``__setattr__`` method always exists, the
``OBJECT`` class has a definition of ``__setattr__``. This base implementation
simply does what setting an attribute did so far, which is write the attribute
into the object's dictionary. This also makes it possible for a user-defined
``__setattr__`` to delegate to the base ``OBJECT.__setattr__`` in some cases.

A test for these two special methods is the following:

```python
def test_getattr():
    # Python code
    class A(object):
        def __getattr__(self, name):
            if name == "fahrenheit":
                return self.celsius * 9. / 5. + 32
            raise AttributeError(name)

        def __setattr__(self, name, value):
            if name == "fahrenheit":
                self.celsius = (value - 32) * 5. / 9.
            else:
                # call the base implementation
                object.__setattr__(self, name, value)
    obj = A()
    obj.celsius = 30
    assert obj.fahrenheit == 86 # test __getattr__
    obj.celsius = 40
    assert obj.fahrenheit == 104

    obj.fahrenheit = 86 # test __setattr__
    assert obj.celsius == 30
    assert obj.fahrenheit == 86

    # Object model code

View on GitHub (pinned to fba689d101)

Solutions

  1. Only read attributes the __getattr__ explicitly handles or that were set beforehand.
  2. Extend __getattr__ to compute or default the additional names you need.
  3. Ensure __getattr__ always re-raises AttributeError(name) for truly unknown names (do not return None).
  4. Use getattr(obj, name, default) to supply a fallback instead of letting it raise.

Example fix

# before
class A(object):
    def __getattr__(self, name):
        if name == 'fahrenheit':
            return self.celsius * 9. / 5. + 32
        raise AttributeError(name)

A().kelvin   # -> AttributeError('kelvin')

# after: handle the new computed name too
def __getattr__(self, name):
    if name == 'fahrenheit':
        return self.celsius * 9. / 5. + 32
    if name == 'kelvin':
        return self.celsius + 273.15
    raise AttributeError(name)
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED = {'fahrenheit', 'celsius'}
name = 'kelvin'
if name not in ALLOWED and name not in obj.__dict__:
    raise AttributeError(name)
getattr(obj, name)

Type guard

def handled_by_getattr(name):
    return name == 'fahrenheit' or name in {'celsius'}

Try / catch

try:
    val = obj.fahrenheit_equivalent
except AttributeError:
    val = None

Prevention

When it happens

Trigger: Accessing any attribute on A other than 'fahrenheit' when that attribute was never set via normal assignment/__setattr__. Because __getattr__ only intercepts missing attributes, already-set names (like celsius) resolve normally and do not reach it.

Common situations: Calling getattr(obj, 'some_unhandled_name') or referencing an uninitialised field; hasattr checks against an object whose __getattr__ only handles a subset of names; serialisation that probes many attribute names.


AI-assisted analysis of aosabook/500lines@fba689d101 (2026-08-13). Data as JSON: /api/errors/230ff7c2ba1e4411. Report an issue: GitHub.