Calling Parent Class Methods in Python Inheritance: __init__, __new__, and __del__

Dec 03, 2025 · Programming · 10 views · 7.8

Keywords: Python inheritance | __init__ method | super function | object-oriented programming | method invocation

Abstract: This article provides an in-depth analysis of method invocation mechanisms in Python object-oriented programming, focusing on __init__, __new__, and __del__ methods within inheritance hierarchies. By comparing initialization patterns from languages like Objective-C, it examines the necessity, optionality, and best practices for calling parent class methods. The discussion covers super() function usage, differences between explicit calls and implicit inheritance, and practical code examples illustrating various behavioral patterns.

Method Invocation in Python Inheritance

In object-oriented programming, inheritance serves as a fundamental mechanism for code reuse. Python, as a dynamic language, implements inheritance with both flexibility and specific rules. When subclasses define __init__, __new__, or __del__ methods, whether to call their parent class counterparts often confuses developers.

Rules for __init__ Method Calls

Python's __init__ method handles object initialization. Unlike languages such as Objective-C, Python does not automatically call the parent class's __init__ method. When a subclass defines its own __init__, it completely overrides the parent's initialization logic.

Consider this example:

class BaseClass:
    def __init__(self):
        self.base_value = 10
        print("BaseClass __init__ called")

class DerivedClass(BaseClass):
    def __init__(self):
        # Without calling parent __init__, base_value remains uninitialized
        self.derived_value = 20
        print("DerivedClass __init__ called")

obj = DerivedClass()
print(obj.derived_value)  # Output: 20
# print(obj.base_value)   # Raises AttributeError

In this case, because DerivedClass's __init__ doesn't call the parent's __init__, the base_value attribute remains uninitialized. To preserve parent class initialization, explicit calling is required:

class ProperDerivedClass(BaseClass):
    def __init__(self):
        super().__init__()  # Python 3 syntax
        # Alternatively: BaseClass.__init__(self)
        self.derived_value = 20
        print("ProperDerivedClass __init__ called")

obj2 = ProperDerivedClass()
print(obj2.base_value)     # Output: 10
print(obj2.derived_value)  # Output: 20

Using the super() Function

The super() function provides a more elegant approach to parent method invocation. In Python 3, super() works correctly without arguments:

class Animal:
    def __init__(self, name):
        self.name = name
        self.is_alive = True

class Mammal(Animal):
    def __init__(self, name, has_fur):
        super().__init__(name)  # Calls Animal.__init__
        self.has_fur = has_fur
        self.warm_blooded = True

class Dog(Mammal):
    def __init__(self, name, breed):
        super().__init__(name, has_fur=True)  # Calls Mammal.__init__
        self.breed = breed
        self.species = "Canis lupus familiaris"

my_dog = Dog("Buddy", "Golden Retriever")
print(f"Name: {my_dog.name}")          # Output: Buddy
print(f"Has fur: {my_dog.has_fur}")    # Output: True
print(f"Breed: {my_dog.breed}")        # Output: Golden Retriever

super() automatically handles method resolution order (MRO) in multiple inheritance scenarios, which direct parent class name calls cannot achieve.

Special Considerations for __new__

The __new__ method differs fundamentally from __init__. As a static method, __new__ creates object instances, while __init__ initializes already created objects.

For __new__, parent class implementation should typically be called:

class CustomObject:
    def __new__(cls, *args, **kwargs):
        # Must call object.__new__ or super().__new__
        instance = super().__new__(cls)
        # Can perform setup before __init__
        instance._created_at = time.time()
        return instance
    
    def __init__(self, value):
        self.value = value

obj = CustomObject(42)
print(obj.value)        # Output: 42
print(obj._created_at)  # Output: Creation timestamp

If parent class __new__ is not called, an object instance must be explicitly returned; otherwise, __init__ won't be invoked.

Considerations for __del__ Method

The __del__ method is called during garbage collection. Similar to __init__, a subclass's __del__ doesn't automatically call the parent's __del__:

class ResourceHolder:
    def __init__(self):
        self.resource = acquire_resource()
    
    def __del__(self):
        release_resource(self.resource)
        print("Resource released")

class ExtendedResourceHolder(ResourceHolder):
    def __init__(self):
        super().__init__()
        self.extra_resource = acquire_extra_resource()
    
    def __del__(self):
        # Must explicitly call parent's __del__
        release_extra_resource(self.extra_resource)
        super().__del__()
        print("ExtendedResourceHolder cleaned up")

However, relying on __del__ for resource cleanup is discouraged. Python's garbage collection timing is unpredictable; better approaches include context managers (with statements) or explicit cleanup methods.

Three Method Invocation Scenarios

Based on Answer 2's analysis, three typical scenarios emerge:

  1. Complete Inheritance: Subclass doesn't define __init__, directly using parent's initialization.
  2. Complete Override: Subclass defines its own __init__ without calling parent method, implementing entirely new initialization.
  3. Extension Enhancement: Subclass defines __init__, calls parent method, then adds additional initialization steps.

These patterns correspond to different design requirements, and developers should choose appropriately based on specific situations.

Best Practice Recommendations

Based on Answer 3 and other responses, the following best practices are recommended:

  1. In Python 3, prefer super().__init__() over explicit parent class name calls.
  2. For the object class, its __init__ and __del__ are empty methods; calling them doesn't affect functionality.
  3. __new__ methods should generally call parent implementations unless special requirements exist.
  4. Avoid over-reliance on __del__; use context managers for resource management.
  5. In multiple inheritance, super() correctly handles MRO, avoiding issues with direct parent name calls.

Understanding these rules helps write more robust, maintainable Python object-oriented code, particularly in complex inheritance hierarchies.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.