AW Dev Rethought

⚖️ There are two ways of constructing a software design: one way is to make it so simple that there are obviously no deficiencies - C.A.R. Hoare

🧠 Python DeepCuts — 💡 The Descriptor Protocol


Description:

If you’ve ever used @property, @classmethod, @staticmethod, ORMs like Django/SQLAlchemy, or dataclasses — you’ve used descriptors.

Descriptors are one of Python’s most powerful and elegant internals.

They give objects the ability to control attribute access at the class level.

Any object implementing one or more of the following:

  • get(self, instance, owner)
  • set(self, instance, value)
  • delete(self, instance)

is a descriptor.

Let’s break down how they work and why they matter.


🧩 What Is a Descriptor?

A descriptor is simply a Python object with a get, set, or delete method.

This example logs when a value is accessed or updated:

class LoggedAttribute:
    def __init__(self, initial):
        self.value = initial

    def __get__(self, instance, owner):
        print("Accessing attribute...")
        return self.value

    def __set__(self, instance, value):
        print("Setting attribute...")
        self.value = value

class Demo:
    x = LoggedAttribute(10)

obj = Demo()
print(obj.x)       # __get__
obj.x = 20         # __set__

Descriptors intercept attribute lookups before Python returns a value.


🧠 @property Is Just a Descriptor

When you define a property, Python turns it into a descriptor behind the scenes.

class Temperature:
    def __init__(self, celsius):
        self._c = celsius

    @property
    def celsius(self):
        return self._c

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Below absolute zero")
        self._c = value
  • The getter acts like get
  • The setter acts like set

@property is one of the most user-friendly wrappers around the descriptor protocol.


🔍 How Methods Work Using Descriptors

When you access a function through a class instance, Python turns it into a bound method.

This happens via descriptors.

class Example:
    def greet(self):
        print("Hello from instance!")

e = Example()
e.greet()   # bound method

Functions implement get, allowing them to bind self automatically.

Without descriptors, self would NEVER be injected automatically.


🧱 staticmethod & classmethod Are Descriptors

Both use descriptors to change how functions receive arguments.

class Tools:
    @staticmethod
    def ping():
        return "Static call"

    @classmethod
    def identify(cls):
        return f"Called on {cls.__name__}"
Decorator What it passes
@staticmethod passes nothing
@classmethod passes cls

Both work because they use descriptors internally.


🏗️ A Practical Example: ORM-Style Field

Frameworks like Django and SQLAlchemy use descriptors to define fields.

class Field:
    def __init__(self):
        self.private_name = None

    def __set_name__(self, owner, name):
        self.private_name = "_" + name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return getattr(instance, self.private_name, None)

    def __set__(self, instance, value):
        setattr(instance, self.private_name, value)

Usage:

class User:
    name = Field()
    age = Field()

u = User()
u.name = "Abhijith"
u.age = 29

This is the core of how ORMs manage attributes.


🔧 set_name: Giving Descriptors Their Attribute Name

Python 3.6 introduced set_name, called automatically when the class is created.

class Label:
    def __set_name__(self, owner, name):
        print(f"Binding descriptor to: {name}")

It is used heavily inside ORM system fields, dataclasses, and attrs.


✅ Key Points

  • Descriptors are the backbone of Python’s attribute system.
  • @property, methods, classmethod, staticmethod → all built on descriptors.
  • ORMs, dataclasses, and frameworks use descriptors for controlled attribute access.
  • set_name helps descriptors know which attribute name they belong to.
  • A descriptor is triggered whenever an attribute is:
    • accessed (get)
    • assigned (set)
    • deleted (delete)

Understanding descriptors unlocks Python’s most advanced patterns.


Code Snippet:

import inspect


class LoggedAttribute:
    def __init__(self, initial):
        self.value = initial

    def __get__(self, instance, owner):
        print("Accessing attribute...")
        return self.value

    def __set__(self, instance, value):
        print("Setting attribute...")
        self.value = value

class Demo:
    x = LoggedAttribute(10)

obj = Demo()
print(obj.x)     # calls __get__
obj.x = 20       # calls __set__
print(obj.x)


class Temperature:
    def __init__(self, celsius):
        self._c = celsius

    @property
    def celsius(self):        # behaves like __get__
        return self._c

    @celsius.setter
    def celsius(self, value): # behaves like __set__
        if value < -273.15:
            raise ValueError("Below absolute zero")
        self._c = value

t = Temperature(25)
print(t.celsius)
t.celsius = 30
print(t.celsius)


class Example:
    def greet(self):
        print("Hello from instance!")

# Unbound function
print(Example.greet)

# Bound method (self will be passed automatically)
e = Example()
print(e.greet)

# Call it
e.greet()


class Tools:
    @staticmethod
    def ping():
        return "Static call"

    @classmethod
    def identify(cls):
        return f"Called on {cls.__name__}"

print(Tools.ping())        # No instance or class passed
print(Tools.identify())    # cls passed automatically


class Field:
    def __init__(self):
        self.private_name = None

    def __set_name__(self, owner, name):
        # called once when class is created
        self.private_name = "_" + name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return getattr(instance, self.private_name, None)

    def __set__(self, instance, value):
        setattr(instance, self.private_name, value)

class User:
    name = Field()
    age = Field()

u = User()
u.name = "Sam"
u.age = 29

print(u.name, u.age)


class Label:
    def __set_name__(self, owner, name):
        print(f"Binding descriptor to: {name}")

    def __get__(self, instance, owner):
        return "constant"

class Demo:
    x = Label()

Link copied!

Comments

Add Your Comment

Comment Added!