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 codeView on GitHub (pinned to fba689d101)
Solutions
- Only read attributes the __getattr__ explicitly handles or that were set beforehand.
- Extend __getattr__ to compute or default the additional names you need.
- Ensure __getattr__ always re-raises AttributeError(name) for truly unknown names (do not return None).
- 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
- Always re-raise AttributeError(name) in __getattr__ for unhandled names.
- Use getattr(obj, name, default) for optional attributes.
- Document the set of names __getattr__ synthesises.
- Prefer __init__ defaults for storage-backed fields over __getattr__ magic.
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.