Attribute Lookup

Something as simple as obj.attr follows a precise, layered algorithm — descriptors, instance dicts, class dicts, and MRO all get a turn before __getattr__ is consulted as a last resort.

Every obj.attr expression looks trivial but triggers a well-defined search implemented by object.__getattribute__: it checks for a data descriptor on the class (and its bases, via the MRO), then the instance's own __dict__, then a non-data descriptor or plain class attribute, and only if all of that fails does it fall back to __getattr__ — finally raising AttributeError if nothing is found. Understanding this order explains why properties can't be shadowed by instance assignment, why methods magically receive self, and how multiple inheritance decides which class's attribute wins.

Instance Attributes vs Class Attributes

An instance attribute lives in that specific object's own __dict__ (or slot, for a class using __slots__) — it's per-object state. A class attribute lives in the class's own __dict__ and is shared by every instance that doesn't have its own instance attribute of the same name overriding it. Assigning self.x = value inside a method creates/updates an *instance* attribute; assigning x = value directly in the class body creates a *class* attribute.

class Dog:
    species = "Canis familiaris"   # class attribute, shared

    def __init__(self, name: str):
        self.name = name             # instance attribute, per-object

rex = Dog("Rex")
fido = Dog("Fido")
print(rex.species, fido.species)   # both see the same shared class attribute
rex.species = "Rex's own species"   # creates an instance attribute, shadowing the class one
print(rex.species, fido.species)   # 'Rex's own species' 'Canis familiaris' -- fido unaffected

The Default Algorithm: __getattribute__

object.__getattribute__(self, name) (invoked automatically for every obj.attr access) implements this precise order, roughly:

This is the general shape of the algorithm as documented for Python's data model; the exact internal C implementation is a CPython detail, but this ordering is the language-level contract every conforming implementation follows.

1. Search type(obj) and its MRO for 'name'.
   - If found AND it's a data descriptor -> call its __get__ and return. DONE.
2. Search obj's own instance __dict__ for 'name'.
   - If found there -> return it directly. DONE.
3. If step 1 found something (a non-data descriptor, or a plain class attribute):
   - If it's a non-data descriptor -> call its __get__ and return. DONE.
   - Otherwise -> return the class attribute itself. DONE.
4. Nothing found anywhere above -> raise AttributeError
   (which triggers __getattr__ if the class defines one).

Descriptor Precedence in Practice

This is exactly why a property (a data descriptor, see the related Descriptors topic) can never be shadowed by assigning into instance.__dict__ under the same name — step 1 (data descriptor on the class) is checked *before* step 2 (instance __dict__). A non-data descriptor (like a plain method) *can* be shadowed by an instance attribute, because step 2 runs before the non-data-descriptor fallback in step 3.

class C:
    @property
    def value(self):
        return 42

c = C()
c.__dict__["value"] = 99   # try to bypass the property
print(c.value)               # still 42 -- property (data descriptor) wins over instance dict

__getattr__: The Fallback Hook

__getattr__(self, name) is only called when the normal lookup (everything above) fails to find name anywhere — it's a fallback, not an override of normal lookup. This makes it the right tool for dynamic/computed attributes that don't otherwise exist: proxy objects, lazy-loading wrappers, or friendly error messages. It must raise AttributeError itself for names it doesn't recognize, so the failure behaves consistently with normal attribute access.

__getattr__ never fires for 'debug'/'timeout' via _data lookups directly -- it fires because 'debug'/'timeout' aren't real attributes of Config, so normal lookup fails first, then __getattr__ is consulted.

This is distinct from __getattribute__, which is called for *every* attribute access unconditionally (and overriding it is rare and easy to get wrong — infinite recursion is a common bug if it isn't carefully delegated back to super().__getattribute__).

class Config:
    def __init__(self, data: dict):
        self._data = data

    def __getattr__(self, name):
        try:
            return self._data[name]
        except KeyError:
            raise AttributeError(f"no config key {name!r}") from None

cfg = Config({"debug": True, "timeout": 30})
print(cfg.debug, cfg.timeout)   # __getattr__ fires: normal lookup found nothing
try:
    cfg.missing
except AttributeError as exc:
    print(exc)

The Method Resolution Order (MRO)

When a class inherits from one or more base classes, 'search the class for name' in step 1 above actually means 'search the class's Method Resolution Order' — a linearized list of the class itself and all its ancestors, computed with the C3 linearization algorithm, which guarantees a consistent order even with multiple inheritance and diamond-shaped hierarchies. ClassName.__mro__ (or ClassName.mro()) shows this order directly; type.__mro__ always lists the class itself first, then its bases in resolution order, ending with object.

C3 linearization respects both the declared base order (Left before Right) and each base's own MRO, producing a single consistent search order.

class Base:
    def greet(self):
        return "Base greet"

class Left(Base):
    def greet(self):
        return "Left greet"

class Right(Base):
    pass

class Child(Left, Right):
    pass

print(Child.__mro__)
print(Child().greet())   # 'Left greet' -- Left comes before Right/Base in the MRO

__setattr__ and __delattr__

Assignment (obj.attr = value) and deletion (del obj.attr) go through their own analogous hooks, __setattr__ and __delattr__, which by default (object.__setattr__) check for a data descriptor's __set__/__delete__ first and otherwise write/remove the entry directly in the instance's __dict__. Overriding these lets a class intercept every assignment (for validation, logging, or making an object effectively immutable), but like __getattribute__, it's easy to cause infinite recursion if the override doesn't eventually delegate to super().__setattr__(name, value) rather than using self.name = value directly inside itself.

Practical Lookup Mental Model

A simplified but reliable mental checklist for obj.attr, in order:

1. Is 'attr' a data descriptor (like property) somewhere in type(obj).__mro__?
   -> yes: use it, done.
2. Is 'attr' in obj.__dict__ (this specific instance's own attributes)?
   -> yes: use it, done.
3. Is 'attr' a non-data descriptor or plain value somewhere in type(obj).__mro__?
   -> yes: use it (binding methods along the way), done.
4. Does type(obj) (or a base) define __getattr__?
   -> yes: call it as a last resort.
5. Nothing found -> AttributeError.

Practical Usage

This machinery is what makes several everyday patterns work: instance attribute assignment in __init__ overriding a class-level default value, calling self.method() automatically passing self (via the non-data-descriptor behavior of functions), a read-only property genuinely staying read-only regardless of what you try to poke into instance.__dict__, and dynamic proxy/wrapper objects built with __getattr__ that forward unknown attribute access to a wrapped object.

class Wrapper:
    def __init__(self, wrapped):
        object.__setattr__(self, "_wrapped", wrapped)   # bypass our own __setattr__

    def __getattr__(self, name):
        return getattr(self._wrapped, name)

    def __setattr__(self, name, value):
        setattr(self._wrapped, name, value)

class Real:
    def __init__(self):
        self.value = 10

w = Wrapper(Real())
print(w.value)   # forwarded via __getattr__
w.value = 20      # forwarded via __setattr__
print(w._wrapped.value)   # 20

Edge Cases

A common bug in a custom __getattr__ or __setattr__: referencing self.something inside the override for an attribute that itself doesn't exist yet causes infinite recursion, because that reference triggers the same override again. Bootstrap any attributes the override needs (like _wrapped above) via object.__setattr__/object.__getattribute__ directly, or set them before the override could possibly be triggered.

hasattr(obj, name) is implemented by calling getattr(obj, name) and checking whether it raises AttributeError — it can trigger __getattr__ (or a property's getter, with side effects) as a byproduct, it isn't just checking a name against a static list.

For a class using __slots__, instances have no __dict__ at all by default; each slot is itself implemented as a data descriptor, which changes some of these interactions (no arbitrary new instance attributes, for instance) but not the fundamental lookup order.

Best Practices

Reach for __getattr__ for genuinely dynamic/computed attributes; don't override __getattribute__ unless you specifically need to intercept *every* access, and always delegate to super().__getattribute__(name) inside it.

When overriding __setattr__, always eventually call object.__setattr__(self, name, value) (or super().__setattr__) rather than self.name = value, to avoid infinite recursion.

Use Class.__mro__ when debugging unexpected multiple-inheritance behavior — it's the definitive, inspectable source of truth for which class an attribute will resolve to.

Prefer composition or explicit delegation methods over a magic __getattr__ proxy when the forwarding logic needs to be selective or well-documented — blanket forwarding can make an object's real interface hard to discover.

Complete Example

Demonstrates all four practical lookup stages: instance dict hit, class attribute fallback, and the __getattr__ last resort, alongside a __setattr__ override that logs assignments.

class LoggingBase:
    def __init__(self):
        self._log: list[str] = []

    def __setattr__(self, name, value):
        if name != "_log":
            self._log.append(f"set {name} = {value!r}")
        object.__setattr__(self, name, value)

class Settings(LoggingBase):
    default_timeout = 30   # class attribute

    def __getattr__(self, name):
        # only called if normal lookup (instance dict + class/MRO) found nothing
        raise AttributeError(f"unknown setting: {name}")

s = Settings()
s.timeout = 10                 # instance attribute, logged via __setattr__
print(s.timeout)                 # found in instance __dict__: step 2
print(s.default_timeout)         # found as a class attribute: step 3
print(s._log)
try:
    s.missing_setting
except AttributeError as exc:
    print(exc)                    # __getattr__ fallback fires: step 4

Quick Reference

obj.attr lookup order:
1. data descriptor found via type(obj).__mro__       -> wins always
2. obj.__dict__[name]                                  -> instance attribute
3. non-data descriptor or plain value via MRO          -> class attribute / method
4. type(obj).__getattr__(obj, name)                     -> fallback, only if 1-3 all miss
5. AttributeError

Class.__mro__            # inspect the resolution order directly
__getattribute__          # called for EVERY access (rarely overridden)
__getattr__                # called only when normal lookup fails
__setattr__/__delattr__    # analogous hooks for assignment/deletion

Key Takeaways

Instance attributes live per-object in obj.__dict__; class attributes live on the class and are shared unless shadowed by an instance attribute.

object.__getattribute__ implements a strict order: data descriptors first, then the instance dict, then non-data descriptors/plain class attributes, then __getattr__ as a last resort.

A data descriptor (like property) always wins over an instance __dict__ entry of the same name; a non-data descriptor (like a plain method) can be shadowed by one.

__getattr__ only fires when normal lookup fails entirely — it's a fallback, not an interception of every access (that's __getattribute__).

With multiple inheritance, Class.__mro__ (C3 linearization) determines which base class's attribute is found first.

Overriding __setattr__/__getattribute__ must delegate to the object/super() version to avoid infinite recursion.

class Wrapper: def __getattr__(self, name): return getattr(self.wrapped, name) # self.wrapped triggers __getattr__ again!

class Wrapper: def __init__(self, wrapped): object.__setattr__(self, "wrapped", wrapped) def __getattr__(self, name): return getattr(self.wrapped, name)

If 'wrapped' were never set as a real instance attribute, referencing self.wrapped inside __getattr__ would fail normal lookup and re-trigger __getattr__, causing infinite recursion.

class C: def __setattr__(self, name, value): self.name = value # calls __setattr__ again -- infinite recursion class C: def __setattr__(self, name, value): object.__setattr__(self, name, value)

Using self.name = value inside __setattr__ itself invokes __setattr__ again; delegate to object.__setattr__ (or super().__setattr__) to actually perform the assignment.

class C: @property def x(self): return 1 c = C() c.__dict__["x"] = 2 print(c.x) # assumed 2, but it's actually 1 property is a data descriptor, so it always wins over an instance __dict__ entry of the same name — writing into __dict__ directly does not shadow it.

What is the difference between an instance attribute and a class attribute?

An instance attribute lives in that specific object's own __dict__ (per-object state); a class attribute lives in the class's __dict__ and is shared by all instances that don't have their own instance attribute of the same name.

In the default attribute lookup order, what is checked first: a data descriptor on the class, or the instance's own __dict__?

A data descriptor on the class (found via the MRO) is checked first and always wins, even over an entry of the same name in the instance's __dict__.

When does __getattr__ actually get called?

Only as a fallback, after normal attribute lookup (data descriptors, instance dict, non-data descriptors/class attributes) has failed to find the name anywhere — it never runs for an attribute normal lookup would have found.

Why can a non-data descriptor (like a plain method) be shadowed by an instance attribute of the same name, but a data descriptor (like property) cannot?

The default algorithm checks the instance __dict__ before falling back to a non-data descriptor, but checks for a data descriptor before ever looking at the instance __dict__ at all.

What determines which base class's attribute is found first in a multiple-inheritance hierarchy?

The class's Method Resolution Order (MRO), computed via C3 linearization and inspectable as Class.__mro__, which respects both declared base order and each base's own MRO.

Why is overriding __setattr__ with a naive self.name = value inside it a bug?

That assignment itself invokes __setattr__ again (since __setattr__ handles all attribute assignment), causing infinite recursion; it must delegate to object.__setattr__ or super().__setattr__ instead.

What's the practical difference between overriding __getattribute__ versus __getattr__?

__getattribute__ is called for every single attribute access unconditionally, making it powerful but easy to break; __getattr__ is only consulted as a fallback when normal lookup has already failed, making it the safer, more common hook for dynamic attributes.

What does this print?

Prints 'A' (because A comes before B in C's declared bases and thus in the MRO), followed by the tuple of classes in resolution order: (C, A, B, Base, object) — C3 linearization respects the declared left-to-right base order.

Write a class Proxy that wraps any object and forwards attribute reads via __getattr__, without causing infinite recursion when storing the wrapped object.

class Proxy: def __init__(self, target): object.__setattr__(self, "_target", target) def __getattr__(self, name): return getattr(self._target, name) class Point: def __init__(self, x, y): self.x = x self.y = y p = Proxy(Point(3, 4)) print(p.x, p.y)

Prints '3 4'. Storing _target via object.__setattr__ bypasses any custom __setattr__ and puts it directly in the instance __dict__, so later self._target lookups inside __getattr__ succeed via normal lookup (step 2) instead of re-triggering __getattr__.

Write a class Frozen whose __setattr__ raises AttributeError for any assignment after __init__ has finished, using an internal flag, without infinite recursion.

class Frozen: def __init__(self, value): object.__setattr__(self, "value", value) object.__setattr__(self, "_frozen", True) def __setattr__(self, name, value): if getattr(self, "_frozen", False): raise AttributeError(f"cannot set {name!r} -- instance is frozen") object.__setattr__(self, name, value) f = Frozen(10) try: f.value = 20 except AttributeError as exc: print(exc)

Prints "cannot set 'value' -- instance is frozen". __init__ uses object.__setattr__ directly to bootstrap value and _frozen without triggering the override's own check; afterward, any assignment goes through the overridden __setattr__, sees _frozen is True, and raises.

class Base:
    def who(self):
        return "Base"

class A(Base):
    def who(self):
        return "A"

class B(Base):
    def who(self):
        return "B"

class C(A, B):
    pass

print(C().who())
print(C.__mro__)