Python
getattr
method overriding
object-oriented programming
Python attributes

How do I override __getattr__ without breaking the default behavior?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In Python, __getattr__ is only called after normal attribute lookup has already failed. That means the easiest way to "not break the default behavior" is to remember that normal behavior already runs first, and your __getattr__ should only provide a fallback for truly missing attributes.

The two rules that matter most are: do not create infinite recursion by touching missing attributes carelessly inside __getattr__, and raise AttributeError when you cannot provide the requested attribute. If you violate either rule, introspection and ordinary attribute access start behaving strangely.

What __getattr__ Actually Does

Python attribute lookup normally checks the instance, the class, and the inheritance chain. Only if that fails does Python call __getattr__.

So this works as a pure fallback hook:

python
1class Config:
2    def __init__(self):
3        self.name = "demo"
4
5    def __getattr__(self, attr):
6        if attr == "timeout":
7            return 30
8        raise AttributeError(attr)
9
10cfg = Config()
11print(cfg.name)
12print(cfg.timeout)

cfg.name is found normally. cfg.timeout is not found, so __getattr__ provides a fallback value.

Always Raise AttributeError for Unknown Names

The correct failure mode for __getattr__ is AttributeError:

python
1class Config:
2    def __getattr__(self, attr):
3        if attr == "timeout":
4            return 30
5        raise AttributeError(f"{type(self).__name__!s} has no attribute {attr!r}")

This matters because Python tools rely on AttributeError to understand whether an attribute genuinely exists. If you return None for everything instead, functions like hasattr, IDE completion, and some libraries can behave incorrectly.

Avoid Infinite Recursion

A classic mistake is accidentally triggering __getattr__ from inside itself:

python
class Broken:
    def __getattr__(self, attr):
        return self.missing_value

If missing_value does not exist, Python calls __getattr__ again, which asks for missing_value again, and so on until recursion fails.

When you need direct attribute access inside your implementation, use object.__getattribute__:

python
1class Safe:
2    def __init__(self):
3        self._values = {"timeout": 30}
4
5    def __getattr__(self, attr):
6        values = object.__getattribute__(self, "_values")
7        if attr in values:
8            return values[attr]
9        raise AttributeError(attr)

That bypasses __getattr__ and avoids the recursive trap.

__getattr__ Versus __getattribute__

This distinction is important:

  • '__getattr__ runs only for missing attributes'
  • '__getattribute__ runs for every attribute access'

If your goal is just to add fallback behavior, prefer __getattr__. Overriding __getattribute__ is much more invasive and much easier to get wrong.

A lot of "breaking default behavior" problems come from using __getattribute__ when __getattr__ would have been enough.

A Realistic Delegation Example

A common use case is proxying unknown attributes to another object:

python
1class Wrapper:
2    def __init__(self, wrapped):
3        self._wrapped = wrapped
4
5    def __getattr__(self, attr):
6        wrapped = object.__getattribute__(self, "_wrapped")
7        return getattr(wrapped, attr)
8
9items = Wrapper([1, 2, 3])
10print(items.count(2))

Here, ordinary attributes on Wrapper still work normally, and only missing ones are delegated to the wrapped object.

When You Need Parent Fallback Behavior

If a base class also defines __getattr__, you can delegate to it explicitly:

python
1class Base:
2    def __getattr__(self, attr):
3        if attr == "base_value":
4            return 10
5        raise AttributeError(attr)
6
7class Child(Base):
8    def __getattr__(self, attr):
9        if attr == "child_value":
10            return 20
11        return super().__getattr__(attr)

That is the right way to extend an inherited fallback instead of silently replacing it.

Common Pitfalls

The biggest pitfall is returning a default value for every unknown attribute. That may feel convenient, but it breaks normal Python expectations and makes debugging much harder.

Another common issue is recursion caused by reading missing attributes through self.some_name inside __getattr__. Use object.__getattribute__ when you need internal state safely.

Developers also forget that __getattr__ is not called for attributes that already exist. If you need to intercept all lookups, that is a __getattribute__ problem, not a __getattr__ one.

Finally, always raise AttributeError for unsupported names. That keeps hasattr, getattr(..., default), and Python's introspection behavior working correctly.

Summary

  • '__getattr__ is a fallback hook for missing attributes, not a replacement for normal lookup.'
  • To preserve default behavior, let normal lookup work first and only handle truly missing names.
  • Raise AttributeError when you cannot provide the requested attribute.
  • Use object.__getattribute__ inside __getattr__ when you need internal state without recursion.
  • Prefer __getattr__ over __getattribute__ unless you really need to intercept every attribute access.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.