Skip to main content

How to Use Destructors in Python (del method)

Unlike C++, Python manages memory automatically through garbage collection. The __del__ method exists but behaves unpredictably, making it unsuitable for most resource cleanup scenarios.

How __del__ Works

The destructor is called when an object's reference count drops to zero:

class Resource:
def __init__(self, name):
self.name = name
print(f"{self.name}: Created")

def __del__(self):
print(f"{self.name}: Destroyed")

obj = Resource("Alpha")
print("Object in use...")
del obj
print("After deletion")

Output:

Alpha: Created
Object in use...
Alpha: Destroyed
After deletion

When __del__ Gets Called

The timing depends on reference counting and garbage collection:

class Item:
def __init__(self, id):
self.id = id
print(f"Item {id} created")

def __del__(self):
print(f"Item {self.id} destroyed")

def create_items():
a = Item(1)
b = Item(2)
return b # Only b survives

result = create_items()
print("Function returned")
# Item 1 destroyed when function exits (out of scope)

result = None # Now Item 2's refcount hits zero
print("Program ending")

Output:

Item 1 created
Item 2 created
Item 1 destroyed
Function returned
Item 2 destroyed
Program ending

The Circular Reference Problem

When objects reference each other, reference counts never reach zero:

class Node:
def __init__(self, name):
self.name = name
self.neighbor = None

def __del__(self):
print(f"{self.name} deleted")

# Create circular reference
a = Node("A")
b = Node("B")
a.neighbor = b
b.neighbor = a

# Delete references
del a
del b
print("Objects deleted from namespace")

# __del__ is NOT called immediately!
# The cyclic garbage collector will eventually clean up,
# but timing is unpredictable

Output:

Objects deleted from namespace
A deleted
B deleted
warning

Circular references prevent immediate cleanup. The cyclic garbage collector handles them eventually, but you cannot rely on when __del__ will execute, or if it will at all before program exit.

Problems with __del__

Several issues make destructors unreliable:

import gc

class Unreliable:
def __init__(self, name):
self.name = name

def __del__(self):
# Problem 1: Exceptions are ignored
raise ValueError("This won't crash the program")

# Problem 2: Global variables may be None during shutdown
# print(some_global) # May fail with AttributeError

# Problem 3: Other objects may already be deleted
# self.dependency.close() # May fail

obj = Unreliable("test")
del obj
print("No exception raised!") # __del__ exceptions are silently ignored

Output:

Exception ignored in: <function Unreliable.__del__ at 0x7fd66acb5440>
Traceback (most recent call last):
File "/home/main.py", line 9, in __del__
raise ValueError("This won't crash the program")
ValueError: This won't crash the program
No exception raised!

The Better Way: Context Managers

For deterministic resource cleanup, use the with statement:

class DatabaseConnection:
def __init__(self, host):
self.host = host
self.connection = None

def __enter__(self):
print(f"Connecting to {self.host}")
self.connection = "active"
return self

def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Closing connection to {self.host}")
self.connection = None
# Return False to propagate exceptions, True to suppress
return False

def query(self, sql):
return f"Executing: {sql}"

# Guaranteed cleanup, even if an exception occurs
with DatabaseConnection("localhost") as db:
print(db.query("SELECT * FROM users"))

Output:

Connecting to localhost
Executing: SELECT * FROM users
Closing connection to localhost

Using contextlib for Simpler Cases

The contextlib module simplifies context manager creation:

from contextlib import contextmanager

@contextmanager
def open_resource(name):
print(f"Acquiring {name}")
resource = {"name": name, "data": []}
try:
yield resource
finally:
print(f"Releasing {name}")
resource["data"].clear()

with open_resource("cache") as r:
r["data"].append("item")
print(f"Using {r['name']}")

Output:

Acquiring cache
Using cache
Releasing cache

Weak References to Break Cycles

Use weakref to prevent circular reference issues:

import weakref

class Parent:
def __init__(self, name):
self.name = name
self.children = []

def __del__(self):
print(f"Parent {self.name} deleted")

class Child:
def __init__(self, name, parent):
self.name = name
# Weak reference doesn't increase refcount
self._parent_ref = weakref.ref(parent)

@property
def parent(self):
return self._parent_ref()

def __del__(self):
print(f"Child {self.name} deleted")

parent = Parent("Alice")
child = Child("Bob", parent)
parent.children.append(child)

del child
del parent
# Both destructors are called because weak reference
# doesn't create a strong cycle

Output:

Parent Alice deleted
Child Bob deleted

When to Actually Use __del__

Legitimate use cases are rare:

class TemporaryFile:
"""Cleanup temp file as last resort (defensive programming)."""

def __init__(self, path):
self.path = path
self._closed = False

def close(self):
if not self._closed:
print(f"Properly closing {self.path}")
# os.remove(self.path)
self._closed = True

def __enter__(self):
return self

def __exit__(self, *args):
self.close()

def __del__(self):
# Backup cleanup if user forgot to close
if not self._closed:
print(f"Warning: {self.path} not properly closed!")
self.close()

# Preferred usage
with TemporaryFile("temp.txt") as f:
pass # Properly closed via __exit__

# If user forgets context manager
leaked = TemporaryFile("leaked.txt")
del leaked # __del__ provides backup cleanup

Output:

Properly closing temp.txt
Warning: leaked.txt not properly closed!
Properly closing leaked.txt
tip

Use __del__ only as a safety net for resources that should have been cleaned up elsewhere. Always provide an explicit close() method or context manager as the primary cleanup mechanism.

Comparison Summary

Aspect__del__ DestructorContext Manager (with)
TriggerGarbage collector (unpredictable)Block exit (immediate)
ReliabilityLowHigh
Exception handlingSilently ignoredProperly propagated
Circular referencesProblematicNot affected
Use caseLast resort cleanupFiles, locks, connections

Best Practices

  1. Prefer context managers for any resource that needs cleanup
  2. Implement close() method for explicit cleanup
  3. Use weakref to break reference cycles
  4. Never rely on __del__ for critical cleanup
  5. Don't access global state in __del__

Python's automatic memory management handles object lifecycle well, but resource management (files, connections, locks) requires explicit patterns like context managers for reliable cleanup.