The Destructor: Cleaning Up with __del__
The exact moment of destruction depends on when Python decides the object is no longer needed.
An Object’s Lifecycle
A Python object has a lifecycle with three broad stages: creation, active use, and destruction. During creation, Python calls __init__ so the object can receive its initial attributes and prepare any resources it needs. During active use, your program accesses the object and calls its methods. When the object is no longer needed, Python destroys it and calls __del__ if the class defines that method.
Initialization with __init__
__init__ is a special method that runs automatically when a new instance of a class is created. It is used to give the object a sensible starting state. Assignments such as self.name = name attach attributes to the particular object being created.
When the final line creates a Device instance, Python automatically runs __init__. The self parameter refers to that particular Device object. The assignments create its name and status attributes, so the object begins in a predictable state rather than requiring later code to add those attributes before use.
Classes and Independent Instances
A class defines the structure and behavior that its instances receive. Each instance has its own separate set of attributes. Therefore, one call to __init__ initializes one specific object through self; another object created from the same class receives its own independent initialization.
The two Counter instances are initialized separately. first receives its own value attribute with a starting value of 1, while second receives its own value attribute with a starting value of 10. The class supplies the initialization rule, but each object receives its own attribute set.
Destruction with __del__
__del__ is a destructor method. Python invokes it when an object is about to be destroyed, giving the object a final opportunity to perform cleanup. The object cannot be kept alive by __del__; the method is a last moment for cleanup before the object vanishes.
class Session:
def __init__(self, name):
self.name = name
print("created", self.name)
def __del__(self):
print("cleaning up", self.name)
session = Session("A")
session = "finished"- 1Python creates a Session object for the call
Session("A"). - 2Python automatically runs
__init__for that object. - 3The object remains available through the variable session.
- 4The variable is reassigned to the string "finished".
- 5The original Session object is no longer needed through that variable, so Python destroys it and calls
__del__if it is ready to be destroyed.
- The class defines both lifecycle methods.
- The constructor stores the name and announces creation.
- The destructor announces final cleanup.
- The first assignment creates a Session instance and runs
__init__. - Reassignment makes the original Session object no longer held by session.
Cleanup Responsibilities
Most Python objects do not need __del__. Python's garbage collector automatically reclaims memory when objects are no longer in use. A destructor becomes useful when an object manages an external resource, such as an open file handle, a database connection, or a network socket. In those cases, __del__ can perform a final cleanup action before the object is destroyed.
This example shows the lifecycle pattern: __init__ prepares the object and records the resource-related state, while __del__ provides a final place for cleanup. The source examples identify files, database connections, and network sockets as resources that may need explicit release. A simple object that only contains ordinary Python attributes generally has no special cleanup task for __del__.
Common Lifecycle Mistakes
Common mistake Treating __init__ as an ordinary method that must be called manually.
What it looks likedevice = Device("Sensor")Why it fails__init__runs automatically when the instance is created.Do this insteadDefine __init__ for initialization and let the class call it during instantiation.
Common mistake Forgetting self when assigning an instance attribute.
What it looks likename = nameWhy it failsThe assignment does not attach the value to the specific object through self.
Do this insteadUse self.name = name when the value belongs to the instance.
Common mistake Initializing some attributes only later during normal use.
What it looks likeAn object is created without setting an attribute that its methods expect.
Why it failsInstances may begin in different or invalid states.
Do this insteadInitialize every attribute the object will use in __init__.
Common mistake Assuming __del__ is needed for every class.
What it looks likeAdding a destructor to a class that only stores ordinary attributes.
Why it failsPython automatically reclaims memory for objects that are no longer in use.
Do this insteadUse __del__ only when the object has a cleanup responsibility such as an external resource.
Common mistake Assuming reassignment merely changes a variable and has no lifecycle effect.
What it looks likesession = "finished" after session held a Session object.Why it failsThe original object may become no longer needed, which can trigger destruction and
__del__.Do this insteadWhen tracing code, track both the variable's new value and the old object's remaining lifecycle.
Trace the Lifecycle
What do you think happens?
A class defines both __init__ and __del__. A variable first receives an instance and is then reassigned to another value. Which lifecycle events should you trace?
Reveal answer
__init__ for creation, then possible __del__ for the original object
Creating the instance automatically runs __init__. Reassigning the variable can make the original object no longer needed, which is one of the situations in which Python destroys the object and calls __del__ if it is defined.
Trace this class on paper. Identify which attributes are created by __init__, which object each self refers to, and what event can lead to __del__ for the first object.
class Ticket:
def __init__(self, number):
self.number = number def __del__(self):
print("cleaning", self.number)ticket = Ticket(1)
ticket = Ticket(2)Hints
- There are two separate calls to the class.
- Each call runs
__init__for its own instance. - The variable is reassigned after the first instance is created.
- Track the first Ticket object separately from the second Ticket object.
Key Takeaways
- 1
__init__runs automatically when a new instance is created. - 2self identifies the specific instance whose attributes are being initialized.
- 3Every instance has its own separate attributes and receives its own call to
__init__. - 4
__del__runs when an object is about to be destroyed, including situations involving reassignment, scope ending, or program termination. - 5Most objects do not need
__del__; it is mainly useful when an object manages an external resource that needs cleanup.
At a glance
__init__establishes an object's initial state during instantiation.- self allows initialization to target the current instance.
- Objects move from creation through active use to destruction.
__del__provides a final cleanup opportunity when Python destroys an object.- Use destructors selectively, especially for objects that manage external resources.