Python Constructor: __init__ and __new__
python constructor: Learn how Python constructors work with __init__ and __new__, how to call parent constructors, and common patterns for robust object initialization.
python constructor requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you define a class in Python, the method that runs when you create an instance is often called the constructor. In practice, that role is split between two methods: __new__ and __init__. Understanding the distinction is essential for writing correct initialization logic, especially when dealing with inheritance, immutable types, or custom instance creation.
What init Actually Does
__init__ is the initializer, not the constructor in the strict sense. It receives the already-created instance as its first argument (self) and is responsible for setting up the instance's attributes. When you write obj = MyClass(arg), Python first calls MyClass.__new__(MyClass, arg) to create a new instance, and then calls __init__ on that instance if it is not None.
class Point: def __init__(self, x, y): self.x = x self.y = y p = Point(3, 4) print(p.x, p.y) # 3 4
In most cases, you only need to override __init__. It is where you validate arguments, assign attributes, and set up any initial state. The instance already exists by the time __init__ runs, so you do not return anything from it; returning a value other than None raises a TypeError.
The Role of new and When to Override It
__new__ is a static method that actually creates and returns the new instance. It receives the class as the first argument and typically delegates to super().__new__(cls) to allocate the object. Overriding __new__ is necessary when you need to control instance creation itself, such as for immutable types, singleton patterns, or when you want to return a cached object.
class Singleton: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance s1 = Singleton() s2 = Singleton() print(s1 is s2) # True
__new__ is also used when subclassing immutable built-ins like int, str, or tuple, because those types do not call __init__ after creation in the same way. For example, to create a custom tuple subclass that normalizes input, you override __new__.
class NormalizedTuple(tuple): def __new__(cls, iterable): normalized = [x.upper() if isinstance(x, str) else x for x in iterable] return super().__new__(cls, normalized) nt = NormalizedTuple(["a", "b"]) print(nt) # ('A', 'B')
Calling Parent Constructors with super()
When a class inherits from another, its __init__ should call the parent's __init__ to ensure the base attributes are initialized. super() returns a proxy object that delegates method calls to the next class in the MRO (method resolution order).
class Animal: def __init__(self, name): self.name = name class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed d = Dog("Rex", "Labrador") print(d.name, d.breed) # Rex Labrador
Failing to call super().__init__() is a common source of bugs. The parent's attributes remain unset, and methods that rely on them will fail later. In multiple inheritance scenarios, super() ensures cooperative initialization, but you must design your classes to accept and forward arguments properly using *args and **kwargs when needed.
Alternative Constructors with @classmethod
Sometimes you want to create an instance from data in a different format, such as a dictionary or a file. Instead of overloading __init__ with optional parameters, you can define class methods that act as alternative constructors. These methods parse the input and return a new instance.
class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def from_string(cls, date_str): year, month, day = map(int, date_str.split("-")) return cls(year, month, day) d = Date.from_string("2025-03-14") print(d.year, d.month, d.day) # 2025 3 14
Using a class method keeps the primary __init__ simple and provides a clear, named way to construct objects from different sources. This pattern is common in libraries that need to parse JSON, CSV, or configuration data.
Common Constructor Mistakes and How to Avoid Them
One frequent mistake is using a mutable default argument in __init__. The default value is evaluated once at function definition time, so all instances share the same list or dictionary.
class ShoppingCart: def __init__(self, items=[]): # wrong self.items = items cart1 = ShoppingCart() cart1.items.append("apple") cart2 = ShoppingCart() print(cart2.items) # ['apple']
The correct approach is to use None as the default and create a new mutable object inside __init__.
class ShoppingCart: def __init__(self, items=None): self.items = items if items is not None else []
Another issue is doing heavy work inside __init__ that could be deferred or cached. For example, reading a configuration file or establishing a network connection during construction makes object creation slow and hard to test. Consider lazy initialization or factory methods instead.
Constructor Performance and Memory Considerations
Every time you create an instance, Python calls __new__ and then __init__. The overhead is small for typical objects, but it matters when you create millions of instances. If you have many small objects with fixed attributes, you can use __slots__ to reduce memory usage and attribute lookup time.
class Point: __slots__ = ("x", "y") def __init__(self, x, y): self.x = x self.y = y
__slots__ prevents the creation of a __dict__ per instance, which saves memory. However, it also prevents adding new attributes dynamically, so use it only when the attribute set is fixed.
Another performance consideration is avoiding unnecessary work in __init__. If an object is created frequently but its initialization is expensive, consider using a factory that reuses instances or caches the expensive setup.
When to Use new vs init
Use __init__ for normal initialization: setting attributes, validating input, and preparing the instance. Use __new__ only when you need to control the instance creation process itself, such as:
- Returning an existing instance (singleton or cached object)
- Subclassing immutable types where
__init__is not called automatically - Creating instances of a class from a different class (e.g., metaclass logic)
In most application code, you will never override __new__. Overriding it without a clear reason adds complexity and can lead to subtle bugs, especially with inheritance. If you find yourself reaching for __new__, first consider whether a class method or a factory function would solve the problem more simply.
A concrete decision rule: if you need to customize the object before it is fully initialized, use __new__. If you only need to set up attributes after creation, use __init__. And if you need multiple ways to create an object, prefer @classmethod alternatives over overloading __init__ with many optional parameters.