A Python property deleter is the method that runs when you delete a managed attribute with del obj.attr (or the equivalent delattr). Below I walk through @property, @name.setter, and @name.deleter, show the small Student pattern end-to-end, then the older property(fget, fset, fdel) spelling and a short mistake list. For classes in general, see the Python class example page first if decorators feel new.
I keep generic decorator theory short and stick to what runs when you delete the public attribute.
Tested on: Python 3.13.3; kernel 6.14.0-37-generic.
What is a deleter in Python?
For a property, the deleter is the optional third leg: when you execute del instance.attr, Python does not simply wipe an instance dict entry for you; it calls the function you wired as fdel / @attr.deleter. That method typically deletes the private backing attribute (for example del self._name). The property object on the class remains; only the per-instance storage you chose to manage goes away—until you assign through the setter again.
Here is the smallest useful shape: del box.label runs the method under @label.deleter, which clears _label.
class Box:
def __init__(self, label):
self._label = label
@property
def label(self):
return self._label
@label.deleter
def label(self):
print("deleter ran")
del self._label
box = Box("inbox")
del box.labelYou should see deleter ran once. After that, reading box.label would fail until you add storage again (for example with a setter).
Python property getter, setter, and deleter
Together they give you one public name with three behaviors:
- Getter (
@property): readobj.name. - Setter (
@name.setter): assignobj.name = value. - Deleter (
@name.deleter): run custom logic when someone writesdel obj.name.
Under the hood this is still the built-in property type; the decorators are syntactic sugar on top of ordinary Python classes.
Create a property using @property
Start with read-only access to a backing attribute you control (by convention _name, one leading underscore). The backing field is usually set in __init__ before the property manages reads and writes.
class Student:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
s = Student("Alice")
print(s.name)You should see Alice. There is no setter yet, so s.name = "Bob" would raise AttributeError: can't set attribute until you add one.
Add a setter using @name.setter
The setter must follow the getter and reuse the same method name.
class Student:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
s = Student("Alice")
s.name = "Bob"
print(s.name)You should see Bob.
Add a deleter using @name.deleter
Attach the deleter to the same property name. Inside, delete the backing field you actually store—not a bare del self.name (that would recurse into the deleter again).
class Student:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@name.deleter
def name(self):
del self._name
student = Student("Alice")
del student.name
try:
print(student.name)
except AttributeError:
print("name is gone until you assign again")You should see name is gone until you assign again. After del student.name, the getter no longer finds _name; assigning student.name = "Carol" would go through the setter and recreate _name.
How del obj.attr calls the deleter
When Python handles del student.name, it resolves name on the class, finds a property with an fdel, and calls that deleter function with self bound to student. The same hook runs when you use delattr(student, "name") or student.__delattr__("name") on the usual deletion paths described in the data model.
All three spellings below end up invoking the same deleter on three fresh instances:
class Tag:
def __init__(self):
self._code = "x"
@property
def code(self):
return self._code
@code.deleter
def code(self):
del self._code
def read_code(obj, label):
try:
obj.code
except AttributeError:
return f"{label}: backing gone"
return f"{label}: still there"
a, b, c = Tag(), Tag(), Tag()
del a.code
delattr(b, "code")
c.__delattr__("code")
print(read_code(a, "del obj.attr"))
print(read_code(b, "delattr"))
print(read_code(c, "__delattr__"))You should see three lines ending with backing gone, one for each of del obj.attr, delattr, and __delattr__. That is the same fdel path in all three cases.
property() function with fget, fset, and fdel
Before decorator syntax, people passed callables into property(). It is the same object you get from @property; use whichever style your team agrees on.
class Student:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
self._name = value
def delete_name(self):
del self._name
name = property(get_name, set_name, delete_name)
s = Student("Dan")
print(s.name)
del s.name
try:
print(s.name)
except AttributeError:
print("deleted via property() form")You should see Dan then deleted via property() form. The official property signature documents fget, fset, fdel, and doc.
Common mistakes with property deleter
- Deleting the wrong storage inside the deleter—for example
del self.nameinstead ofdel self._name, which recurses or targets the wrong object. - Adding
@name.deleterbefore you define@propertyforname—the deleter decorator hangs off the property object created by the getter. - Calling
person.name()like a method—access isperson.namewithout parentheses. - Thinking
delremoves the property from the class—it clears the managed slot you implement, not the descriptor onStudent.__dict__. - Reading the property immediately after delete without catching
AttributeErroror reassigning first. - Defaulting to double-underscore mangling (
__name) for “privacy” when a single_nameis clearer for deleters and subclasses. - Wrapping every public field in
propertywhen a plain attribute would stay simpler.
Python property deleter quick reference table
| Need | What you write |
|---|---|
| Managed read | @property + def name(self): ... |
| Managed write | @name.setter + def name(self, value): ... |
| Managed delete | @name.deleter + def name(self): del self._name |
| User deletes value | del obj.name |
| Delete by dynamic string | delattr(obj, "name") |
| Backing field convention | self._name (one underscore) |
| Read after delete | Expect AttributeError until you assign again |
| Older style | name = property(get, set, delete) |
Summary
A property deleter is the fdel you attach with @name.deleter (or the third argument to property). When someone runs del obj.name, Python calls that deleter so you can drop your backing storage—typically del self._name—while the property descriptor itself stays on the class. Pair getter, setter, and deleter on the same public name, keep one consistent storage attribute, and reserve this pattern for when you truly need validation or deletion side effects; otherwise a normal attribute stays easier to read.

