The Destructor: Cleaning Up with __del__
__init__ is a special method that runs automatically when an object is instantiated, marking the beginning of the object's lifecycle.
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"
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
Treating __init__ as an ordinary method that must be called manually.
__init__ runs automatically when the instance is created.
Fix:
Define __init__ for initialization and let the class call it during instantiation.Forgetting self when assigning an instance attribute.
The assignment does not attach the value to the specific object through self.
Fix:
Use self.name = name when the value belongs to the instance.Initializing some attributes only later during normal use.
Instances may begin in different or invalid states.
Fix:
Initialize every attribute the object will use in __init__.Assuming __del__ is needed for every class.
Python automatically reclaims memory for objects that are no longer in use.
Fix:
Use __del__ only when the object has a cleanup responsibility such as an external resource.Assuming reassignment merely changes a variable and has no lifecycle effect.
The original object may become no longer needed, which can trigger destruction and __del__.
Fix:
When 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
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
- __init__ runs automatically when a new instance is created.
- self identifies the specific instance whose attributes are being initialized.
- Every instance has its own separate attributes and receives its own call to __init__.
- __del__ runs when an object is about to be destroyed, including situations involving reassignment, scope ending, or program termination.
- Most objects do not need __del__; it is mainly useful when an object manages an external resource that needs cleanup.
Key Takeaways
- __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.