SoFunction
Updated on 2025-04-22

Deep analysis of the use of __init__ method in Python

In Python's object-oriented programming (OOP) system, the __init__ method is like the "groundbreaking ceremony" when building a house - it defines the initial state of the object when it is born. This seemingly simple constructor actually contains the core philosophy of Python object lifecycle management. This article will peel off the threads and guide you to understand the essence, working mechanism and advanced application skills of the __init__ method.

1. Genetic map of __init__

The official name of __init__ is "Instance Initialization Method", but a more accurate understanding should be "Object State Configurator". When creating an instance through a class (such as obj = MyClass()), the Python interpreter will automatically trigger the following process:

  • Memory allocation: Call the __new__ method to allocate instance memory (default inherited from object)
  • Initialization call: Automatically execute the __init__(self) method
  • Object Return: Return the initialized instance to the caller

Notable hidden details:

  • init__ does not really create instances, the one who is really responsible for creating is __new
  • __init__ must return None, otherwise it will cause TypeError
  • Python provides empty implementations even if __init__ is not explicitly defined

Code Verification:

class Test:
    def __new__(cls):
        print("__new__ called")
        return super().__new__(cls)
    
    def __init__(self):
        print("__init__ called")
 
t = Test()
#Output:# __new__ called
# __init__ called

2. Magical moments in the initialization process

__init__ execution timing is hidden. Understanding these key points can avoid 90% of initialization errors:

Initialization order in the inheritance chain

When multiple inheritances exist, the order of call to __init__ follows MRO (method parsing order). The call chain can be viewed through ().

class A:
    def __init__(self):
        print("A init")
 
class B(A):
    def __init__(self):
        print("B init")
        super().__init__()
 
class C(A):
    def __init__(self):
        print("C init")
        super().__init__()
 
class D(B, C):
    def __init__(self):
        print("D init")
        super().__init__()
 
d = D()
#Output:# D init
# B init
# C init
# A init

The secret of self parameter

self is not a keyword, it is just the first parameter name conventionally. It actually points to the instance itself, and the properties can be bound by self:

class Dog:
    def __init__(self, name, age):
         = name  # instance attribute binding        self._age = age    # Conventional protection attributes

The trap of default parameters

Using mutable default parameters (such as lists, dictionaries) in __init__ will lead to unexpected sharing:

class BadClass:
    def __init__(self, values=[]):
         = values
 
a = BadClass()
(1)
b = BadClass()
print()  # Output [1] instead of the expected []

Correct way to do it:

class GoodClass:
    def __init__(self, values=None):
         = values if values is not None else []

3. Advanced application techniques of __init__

1. Factory model implementation

By combining the __init__ class method, you can create a flexible factory:

class Shape:
    def area(self):
        raise NotImplementedError
 
class Circle(Shape):
    def __init__(self, radius):
         = radius
    
    def area(self):
        return 3.14 *  ** 2
 
class ShapeFactory:
    @classmethod
    def create_shape(cls, shape_type, *args):
        if shape_type == 'circle':
            return Circle(*args)
        # Extend other shapes 
circle = ShapeFactory.create_shape('circle', 5)
print(())  # Output 78.5

2. Attribute verification and conversion

Perform data checksum type conversion in __init__:

class Temperature:
    def __init__(self, celsius):
        if not isinstance(celsius, (int, float)):
            raise TypeError("Temperature must be numeric")
         = celsius
         = celsius * 9/5 + 32
 
t = Temperature(25)
print()  # Output 77.0

3. Delay initialization mode

For complex initialization processes, delayed loading can be used:

class DatabaseConnection:
    def __init__(self, config):
         = config
        self._connection = None  # Delay initialization    
    @property
    def connection(self):
        if not self._connection:
            self._connection = self._create_connection()
        return self._connection
    
    def _create_connection(self):
        # Actual connection logic        print("Creating real connection")
        return "Connection Object"
 
db = DatabaseConnection({"host": "localhost"})
print()  # Create a connection on the first callprint()  # Subsequent calls use existing connections

4. __init__ performance optimization secret

Avoid recalculation

For fixed value calculations, they should be done at the class level rather than the instance level:

# Inefficient implementationclass BadCircle:
    def __init__(self, radius):
         = radius
         = 3.1415926  #Each instance is created 
#Efficient implementationclass GoodCircle:
    PI = 3.1415926  # Class attributes, all instances are shared    def __init__(self, radius):
         = radius

Optimize memory using __slots__

For classes with fixed attributes, using __slots__ can significantly reduce memory usage:

class Point:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
         = x
         = y
 
# AttributeError is triggered by trying to add a new attribute# p = Point(1,2)
# = 3 # Report an error

Initialization parameter unpacking

When processing variable parameters, use *args and **kwargs:

class Vector:
    def __init__(self, *components):
         = components
    
    def magnitude(self):
        return sum(x**2 for x in )**0.5
 
v = Vector(3,4)
print(())  # Output 5.0

5. Common errors and debugging skills

1. Forgot to call parent class __init__

In inheritance, if the subclass defines __init__, the parent class initialization needs to be explicitly called:

class Parent:
    def __init__(self):
         = 42
 
class Child(Parent):
    def __init__(self):
        # super().__init__() # Missing this line of code will cause AttributeError        print()
 
c = Child()  # Error: 'Child' object has no attribute 'value'

2. Circular Dependency Trap

In a complex inheritance system, avoid __init__ loop calls:

class A:
    def __init__(self):
         = B()  # Create B instance 
class B:
    def __init__(self):
         = A()  # Create instance A again, resulting in infinite recursion 
# a = A() # will raise a RecursionError

3. Debugging skills

  • Tracking the initialization process using print statement
  • Setting breakpoint debugging via pdb
  • Use the inspect module to view the class structure
import inspect
 
class MyClass:
    def __init__(self):
        pass
 
print((MyClass, predicate=))
# Output: ['__init__']

Conclusion: Philosophical Thoughts of __init__

__init__ is not only a technical detail, but also reflects Python's design philosophy:

  • Explicit over implicit: Force the developer to clarify the object state
  • Simplicity is better than complex: Achieve powerful functions through simple mechanisms
  • Pragmatism first: Allows flexibility to override default behavior

A deep understanding of the __init__ method is like mastering the "force" of the Python object world. When you write class MyClass: next time you write class MyClass:, remember: the quality of the initialization code often determines the robustness and maintainability of the entire class system.

This is the article about the in-depth analysis of the use of __init__ method in Python. For more related content of Python __init__ method, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!