Advanced · Lesson 18 of 18 · 22 min

Internals

Descriptors, the MRO, and how attribute lookup actually works.

  • Trace attribute lookup through descriptors
  • Read a C3 MRO
  • Use property as a descriptor you already know

Attribute lookup is a protocol

`obj.x` is not a dictionary peek. Python looks for a data descriptor on the class (something with `__get__` and `__set__` or `__delete__`), then the instance dict, then a non-data descriptor (plain functions, which is how methods bind `self`), then the class and its bases. Functions are descriptors: `C.method` is the raw function; `instance.method` is a bound method.

`property` is a data descriptor. `@property` makes a getter; `.setter` attaches a setter. That is why `self.x = 1` inside a property setter does not recurse if you store `self._x`.

property is a descriptor
class Celsius:
    def __init__(self, c):
        self.c = c

    @property
    def f(self):
        return self.c * 9 / 5 + 32

    @f.setter
    def f(self, value):
        self.c = (value - 32) * 5 / 9

t = Celsius(0)
print(t.f)
t.f = 212
print(t.c)

MRO is C3 linearization

Multiple inheritance is legal. Python computes a Method Resolution Order that is consistent, monotonic, and prefers local precedence. `Class.__mro__` or `Class.mro()` shows the search order. `super()` does not mean 'my parent' — it means 'the next class in the MRO of the actual instance'. That is why cooperative `super().__init__` chains work in diamond graphs.

If you cannot explain the MRO, do not multiply inherit implementation. Mixins should be small, documented, and appear before the concrete base: `class T(JsonMixin, Base):`.

A diamond, and super
class A:
    def who(self):
        return "A"

class B(A):
    def who(self):
        return "B>" + super().who()

class C(A):
    def who(self):
        return "C>" + super().who()

class D(B, C):
    def who(self):
        return "D>" + super().who()

print(D.__mro__)
print(D().who())

What CPython is

CPython objects are C structs with a type pointer and a reference count, plus a generational garbage collector for cycles. `id(x)` is typically the object's address. Interning (small ints, some strings) is an implementation detail. PyPy, GraalPy, and others exist; write to the language, not to the optimizer, until a profiler says otherwise.

You now have the spine of the language: names and objects, control, data, functions, classes, types, concurrency, and the lookup rules those rest on. The standard library is the next mountain — `pathlib`, `json`, `datetime`, `collections`, `itertools`, `functools`, `asyncio`, `unittest`/`pytest`. Read those modules the same way: objects, protocols, and a few sharp edges.

Check yourself

Quiz

  1. 1.A bound method exists because functions are:

  2. 2.super() in a method follows:

  3. 3.property is classified as:

Practice

Exercise

Write a class `Box` with a property `value`. The getter returns the stored value (default None). The setter rejects None with ValueError and otherwise stores the value. Include a method `clear()` that sets the stored value back to None without going through the setter.

Tutor

Ask a question about this lesson. Answers are generated on demand and capped so the session stays focused.