Descriptors: __get__, __set__, __delete__
Implement the descriptor protocol for attribute access control.
Descriptors: __get__, __set__, __delete__ is a free Python Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Python Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is a Descriptor?
A descriptor is an object that defines __get__, __set__, or __delete__ and is assigned as a class attribute. Python calls these methods on attribute access.
class Descriptor:
def __get__(self, obj, objtype=None):
print(f"__get__ called, obj={obj}")
return 42
class MyClass:
attr = Descriptor()
print(MyClass().attr) # __get__ called ... 42Data vs Non-Data Descriptors
Data descriptors define __set__ or __delete__ and take precedence over the instance __dict__. Non-data descriptors (only __get__) yield to __dict__.
class DataDesc:
def __get__(self, obj, t): return "data"
def __set__(self, obj, val): pass
class NonDataDesc:
def __get__(self, obj, t): return "nondata"
class C:
d = DataDesc()
n = NonDataDesc()
c = C()
c.__dict__["n"] = "instance" # shadows the non-data desc
c.__dict__["d"] = "instance" # does NOT shadow data desc
print(c.n) # instance
print(c.d) # data__get__ Signature
__get__(self, obj, objtype): obj is the instance (or None when accessed on the class); objtype is the class.
class Verbose:
def __get__(self, obj, objtype=None):
if obj is None:
return self # accessed on class
return f"value for {obj!r}"
class Widget:
label = Verbose()
print(Widget.label) # <Verbose object>
print(Widget().label) # value for <Widget object>__set__ and Validation
Implement __set__(self, obj, value) to validate or transform a value before storing it.
class PositiveInt:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, t):
return obj.__dict__.get(self.name)
def __set__(self, obj, val):
if not isinstance(val, int) or val <= 0:
raise ValueError(f"{self.name} must be a positive int")
obj.__dict__[self.name] = val
class Rect:
width = PositiveInt()
height = PositiveInt()
r = Rect()
r.width = 10
# r.width = -1 # ValueError__set_name__
Python 3.6+ calls __set_name__(owner, name) on the descriptor when it is assigned to a class, giving it access to its own attribute name.
class Typed:
def __set_name__(self, owner, name):
self.public = name
self.private = "_" + name
def __get__(self, obj, t):
return None if obj is None else getattr(obj, self.private, None)
def __set__(self, obj, val):
setattr(obj, self.private, val)
class User:
name = Typed()
u = User()
u.name = "Alice"
print(u.name) # Alice__delete__ for Attribute Removal
Define __delete__(self, obj) to intercept del obj.attr.
class Protected:
def __set_name__(self, owner, name): self.name = name
def __get__(self, obj, t):
return obj.__dict__.get(self.name)
def __set__(self, obj, v): obj.__dict__[self.name] = v
def __delete__(self, obj):
raise AttributeError(f"Cannot delete {self.name}")
class Config:
host = Protected()
c = Config(); c.host = "localhost"
# del c.host # AttributeErrorFunctions Are Non-Data Descriptors
Functions implement __get__ to return a bound method when accessed on an instance. This is how Python's method binding works.
def greet(self):
return f"Hello from {self}"
class C: pass
C.greet = greet
c = C()
print(c.greet()) # Hello from <C object>classmethod and staticmethod Are Descriptors
classmethod and staticmethod are built-in descriptor classes that wrap functions and alter the __get__ return value.
class MyClass:
@classmethod
def from_string(cls, s):
return cls()
# classmethod.__get__ returns a bound method with cls
# staticmethod.__get__ returns the plain functionLazy Attribute Descriptor
Build a descriptor that computes a value once and caches it per instance.
class LazyAttr:
def __init__(self, func): self.func = func
def __set_name__(self, owner, name): self.name = name
def __get__(self, obj, t):
if obj is None: return self
val = self.func(obj)
obj.__dict__[self.name] = val # shadows descriptor
return val
class Report:
@LazyAttr
def summary(self):
print("computing...")
return "done"
r = Report()
print(r.summary) # computing... done
print(r.summary) # done (cached)property Is a Descriptor
property is itself a data descriptor implemented in C. Understanding descriptors helps you understand how @property, @classmethod, and @staticmethod all work.
class Circle:
def __init__(self, r): self._r = r
@property
def radius(self): return self._r
@radius.setter
def radius(self, v):
if v < 0: raise ValueError
self._r = v
# property is: property.__get__ = fget, property.__set__ = fsetDescriptor Lookup Order
Python attribute lookup order: 1) data descriptors from type (MRO), 2) instance __dict__, 3) non-data descriptors and class variables.
# Pseudocode for obj.attr:
# 1. Check type(obj).__mro__ for a data descriptor
# 2. Check obj.__dict__
# 3. Check type(obj).__mro__ for non-data descriptor or class var
# 4. Raise AttributeErrorQuick Check
Which type of descriptor takes precedence over the instance __dict__?
Recap
Descriptors intercept attribute access via __get__, __set__, __delete__. Data descriptors shadow instance __dict__; non-data ones do not. Use __set_name__ to learn the attribute name. property, classmethod, and staticmethod are all descriptors.
Frequently asked questions
Is the “Descriptors: __get__, __set__, __delete__” lesson free?
Yes — the full text of “Descriptors: __get__, __set__, __delete__” is free to read here on the web, and the Python Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Python Academy course, upgrade to CoddyKit PRO.
What will I learn in “Descriptors: __get__, __set__, __delete__”?
Implement the descriptor protocol for attribute access control. You practise Python Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Python Academy?
No prior experience is required. Python Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Descriptors: __get__, __set__, __delete__” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Python Academy lesson?
Yes. Every Python Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- How Python Classes Are Created
- Writing Custom Metaclasses
- Descriptors: __get__, __set__, __delete__
- __slots__ and Memory Optimization