DevAcademia
C++C#CPythonJava
  • Python Fundamentals

  • Introduction to Python
  • Getting Started with Python
  • Python Syntax
  • Python Comments
  • Python Variables
  • Python Data Types
  • Python Numbers
  • Python Casting
  • Python Strings
  • Python Booleans
  • Python Operators
  • Python Lists
  • Python Tuples
  • Python Sets
  • Python Dictionaries
  • Python If...Else
  • Python Match
  • Python While Loops
  • Python For Loops
  • Python Functions
  • Python Lambda
  • Python Arrays
  • Python OOP

  • Python OOP
  • Python Constructors
  • Python Destructors
  • Python Classes/Objects
  • Python Inheritance
  • Python Polymorphism
  • Python Quiz

  • Python Fundamentals Quiz
  • Python Fundamentals

  • Introduction to Python
  • Getting Started with Python
  • Python Syntax
  • Python Comments
  • Python Variables
  • Python Data Types
  • Python Numbers
  • Python Casting
  • Python Strings
  • Python Booleans
  • Python Operators
  • Python Lists
  • Python Tuples
  • Python Sets
  • Python Dictionaries
  • Python If...Else
  • Python Match
  • Python While Loops
  • Python For Loops
  • Python Functions
  • Python Lambda
  • Python Arrays
  • Python OOP

  • Python OOP
  • Python Constructors
  • Python Destructors
  • Python Classes/Objects
  • Python Inheritance
  • Python Polymorphism
  • Python Quiz

  • Python Fundamentals Quiz

Loading Python tutorial…

Loading content
Python OOPTopic 73 of 77
←PreviousPrevNextNext→

Python Destructors

Introduction to Destructors in Python

In Python, destructors are special methods that are automatically called when an object is about to be destroyed. They provide a mechanism to perform cleanup operations before an object is garbage collected.

Python uses automatic garbage collection, and destructors are called non-deterministically as objects become unreachable. Since Python 3.4 (PEP 442), objects in reference cycles that define destructors can be collected and finalized, but the order and exact timing are not guaranteed.

Destructor Syntax and Characteristics

- Destructors are defined using the `__del__` method

- They are called automatically when an object is garbage collected

- They cannot take parameters (except self)

- They cannot return values

- They are not guaranteed to be called immediately when an object goes out of scope

- They may not be called at all in certain situations (e.g., interpreter shutdown)

- CPython detail: in CPython, `__del__` often runs as soon as an object’s reference count drops to zero; on other implementations (e.g., PyPy, Jython) it may be deferred until a GC cycle

- Exceptions raised inside `__del__` are suppressed (reported to stderr) and do not propagate—avoid using them for control flow

- During interpreter shutdown, module globals may already be set to `None` when `__del__` runs—avoid relying on globals

Basic Destructor Example

This example shows a simple class with a destructor that prints a message when the object is garbage collected.

Example
class SimpleClass:
    def __init__(self, name):
        self.name = name
        print(f"Constructor called for {self.name}")
    
    # Destructor
    def __del__(self):
        print(f"Destructor called for {self.name}")

# Create and destroy objects
def test_destructor():
    obj1 = SimpleClass("TestObject1")
    obj2 = SimpleClass("TestObject2")
    
    # Delete references to trigger garbage collection
    del obj1
    print("obj1 deleted")
    
    # obj2 will be deleted when function exits

if __name__ == "__main__":
    test_destructor()
    print("Function completed")
Output
Constructor called for TestObject1
Constructor called for TestObject2
Destructor called for TestObject1
obj1 deleted
Function completed
Destructor called for TestObject2

Context Managers for Deterministic Cleanup

For deterministic resource cleanup, Python provides context managers using the `with` statement. This ensures resources are properly released even if exceptions occur.

Example
class FileHandler:
    def __init__(self, filename):
        self.filename = filename
        self.file = None
        print(f"FileHandler created for {filename}")
    
    def __enter__(self):
        self.file = open(self.filename, 'w')
        print(f"File {self.filename} opened")
        return self
    
    def write_data(self, data):
        if self.file:
            self.file.write(data)
            print(f"Data written to {self.filename}")
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
            print(f"File {self.filename} closed")
    
    def __del__(self):
        print(f"Destructor called for {self.filename}")

# Using context manager for deterministic cleanup
def use_context_manager():
    with FileHandler("data.txt") as fh:
        fh.write_data("Hello, World!")
    # File is automatically closed here
    print("Context manager block exited")

if __name__ == "__main__":
    use_context_manager()
Output
FileHandler created for data.txt
File data.txt opened
Data written to data.txt
File data.txt closed
Context manager block exited
Destructor called for data.txt

Circular References and Garbage Collection

Python's garbage collector handles circular references. Since Python 3.4, even objects that define `__del__` and participate in cycles can be collected and finalized. However, the finalization order among mutually-referencing objects is undefined, and destructors may not run immediately.

Example
class Node:
    def __init__(self, name):
        self.name = name
        self.next = None
        print(f"Node {name} created")
    
    def __del__(self):
        print(f"Node {self.name} destroyed")

def test_circular_reference():
    # Create circular reference
    node1 = Node("A")
    node2 = Node("B")
    
    node1.next = node2
    node2.next = node1  # Circular reference
    
    # Delete references - objects might not be immediately collected due to the cycle
    del node1
    del node2
    
    print("References deleted - garbage collection may run later")

if __name__ == "__main__":
    test_circular_reference()
    # Force garbage collection to see destructors called
    import gc
    gc.collect()
    print("Garbage collection forced")
Output
Node A created
Node B created
References deleted - garbage collection may run later
Node B destroyed
Node A destroyed
Garbage collection forced

Using weakref.finalize for Safer Finalization

`weakref.finalize` lets you register a callback to run when an object becomes unreachable, without keeping it alive and without relying on `__del__`. It works well with cycles and avoids many pitfalls of destructors.

Example
import weakref

class Resource:
    def __init__(self, name):
        self.name = name
        self._closed = False
        # Register a finalizer callback that runs at object finalization time
        self._finalizer = weakref.finalize(self, Resource._cleanup, name)
        print(f"Resource {name} created")

    def close(self):
        if not self._closed:
            self._closed = True
            print(f"Closing {self.name}")
            # Ensure cleanup runs now (finalizer runs at most once)
            self._finalizer()

    @staticmethod
    def _cleanup(name):
        print(f"Finalizing {name}")

# Example usage
def use_finalize():
    r = Resource("R1")
    # No explicit close; finalizer may run later when r becomes unreachable

if __name__ == "__main__":
    use_finalize()
    import gc; gc.collect()
    print("GC cycle completed")
Output
Resource R1 created
Finalizing R1
GC cycle completed

Async Context Managers

For asynchronous code, use async context managers (`async with`) that implement `__aenter__` and `__aexit__` (or `@asynccontextmanager`). This provides deterministic cleanup around `await`-driven operations.

Example
import asyncio
from contextlib import asynccontextmanager

@asynccontextmanager
async def async_resource(name):
    print(f"Opening {name}")
    try:
        yield name
    finally:
        print(f"Closing {name}")

async def main():
    async with async_resource("net-conn") as res:
        print(f"Using {res}")
        await asyncio.sleep(0.1)

if __name__ == "__main__":
    asyncio.run(main())
Output
Opening net-conn
Using net-conn
Closing net-conn

Exception Handling in Destructors

Exceptions in destructors can be problematic since they're called during garbage collection. It's best to avoid complex operations in `__del__` methods. If you must handle risky logic, catch and suppress exceptions explicitly (they are otherwise ignored and printed to stderr).

Example
class RiskyClass:
    def __init__(self, name):
        self.name = name
        print(f"{name} created")
    
    def __del__(self):
        print(f"Destructor called for {self.name}")
        # Better approach: handle exceptions gracefully
        try:
            # Potentially risky cleanup code
            print(f"Cleaning up {self.name}")
        except Exception as e:
            # Avoid raising from __del__
            print(f"Error in destructor: {e}")

def test_exception_handling():
    obj = RiskyClass("TestObject")
    del obj
    print("Object deleted")

if __name__ == "__main__":
    test_exception_handling()
Output
TestObject created
Destructor called for TestObject
Cleaning up TestObject
Object deleted

Best Practices for Resource Management

- Prefer context managers (`with` statements) over destructors for deterministic cleanup

- Use `__del__` only for non-critical, best-effort cleanup

- Avoid complex operations and exceptions in destructors; exceptions are suppressed

- For file handles, network connections, and other resources, use (sync or async) context managers

- Be aware that destructors may not be called during interpreter shutdown and globals may be `None`

- Use `weakref.finalize` for cleanup tied to object lifetime, especially in the presence of cycles

- Consider the `atexit` module for process-exit cleanup, noting it won’t run on hard kills and may see partially torn-down modules

Comparison Table

ApproachDeterministicUse CaseReliability
`__del__` methodNoNon-critical cleanupLow
Context managersYesResource managementHigh
`try-finally` blocksYesException-safe cleanupHigh
`atexit` moduleYes (at process exit)Program exit cleanupMedium–High
`weakref.finalize`No (timing by GC)Cleanup on object finalization; handles cyclesHigh (for its purpose)
Async context managersYesAsync resource managementHigh
Test your knowledge: Python Destructors
Quiz Configuration
4 of 8 questions
Sequential
Previous allowed
Review enabled
Early close allowed
Estimated time: 5 min
Python OOPTopic 73 of 77
←PreviousPrevNextNext→