Swiftorial Logo
Home
Swift Lessons
Tutorials
Learn More
Career
Resources

Python FAQ: Top Questions

49. Explain the difference between `__init__` and `__new__` in Python classes.

In Python's object creation process, `__new__` and `__init__` are two distinct special methods that play different but complementary roles. Understanding their difference is crucial for advanced class customization, especially when dealing with immutable types or controlling object instantiation.

1. `__new__(cls, *args, **kwargs)`: The Constructor/Instance Creator

  • **Purpose:** `__new__` is responsible for **creating and returning a new, empty instance** of the class. It's the "constructor" in the sense that it builds the object itself.
  • **When it's Called:** It's the first method to be called in the object instantiation process.
  • **Parameters:**
    • `cls`: The first argument is always the class for which the instance is being created (e.g., `MyClass` when `MyClass(...)` is called).
    • `*args`, `**kwargs`: Any arguments passed to the class constructor (e.g., `MyClass(arg1, arg2)`).
  • **Return Value:**
    • It must **return a new instance of the class** (or an instance of a subclass). If it doesn't, `__init__` will not be called.
    • Typically, you'll call `super().__new__(cls, *args, **kwargs)` to delegate the actual object creation to the parent class's `__new__` (usually `object.__new__`).
  • **Use Cases:**
    • **Controlling Instance Creation:** When you need to customize *how* the instance is created. This is rare.
    • **Implementing Singletons:** To ensure only one instance of a class ever exists.
    • **Implementing Immutable Objects:** Since `__new__` is called before `__init__`, you can control the immutability of the object during its creation, especially for built-in immutable types like `int`, `str`, `tuple`. You can't change these values in `__init__`.
    • **Subclassing Immutable Built-ins:** When subclassing built-in immutable types (e.g., `str`, `int`, `tuple`), you *must* override `__new__` to correctly create the instance.
    • **Metaclasses:** Metaclasses override their `__new__` method to control the creation of *classes* themselves.

2. `__init__(self, *args, **kwargs)`: The Initializer

  • **Purpose:** `__init__` is responsible for **initializing the newly created instance**. It sets up the object's initial state by assigning values to its attributes. It does not create the object.
  • **When it's Called:** It's called *after* `__new__` has successfully created the instance.
  • **Parameters:**
    • `self`: The first argument is always the newly created instance of the class.
    • `*args`, `**kwargs`: Any arguments passed to the class constructor (`MyClass(arg1, arg2)`).
  • **Return Value:**
    • It must **not return anything** (or explicitly return `None`). Returning anything else will result in a `TypeError`.
  • **Use Cases:**
    • **Setting Initial Attribute Values:** The most common use. Assigning parameters from the constructor to instance variables (e.g., `self.name = name`).
    • **Performing Setup Operations:** Any setup that involves the newly created object's state but doesn't alter its fundamental identity or type.

Summary Table:

Feature `__new__` `__init__`
Role **Creates** the new instance (constructor) **Initializes** the existing instance
When called First, before `__init__` Second, after `__new__` returns an instance
First Argument `cls` (the class itself) `self` (the instance)
Return Value Must return the new instance (or an instance of a subclass) Must return `None` (or implicitly `None`)
Use Cases Subclassing immutable types, singletons, controlling object creation. Setting initial attribute values, performing setup related to instance state.
Frequency of Use Rarely overridden in typical applications Commonly overridden in almost every class

In most everyday Python programming, you only need to concern yourself with `__init__`. You only delve into `__new__` when you need to control the very process of object creation itself, such as creating objects that are immutable or implementing design patterns like the singleton.


# --- Example 1: Basic Class with __init__ (common case) ---
print("--- Basic Class with __init__ ---")

class MyObject:
    def __init__(self, value):
        print(f"MyObject __init__ called with value: {value}")
        self.value = value

obj1 = MyObject(10)
print(f"obj1.value: {obj1.value}")


# --- Example 2: Overriding __new__ and __init__ ---
print("\n--- Overriding __new__ and __init__ ---")

class CustomObject:
    def __new__(cls, *args, **kwargs):
        print(f"CustomObject __new__ called. cls: {cls.__name__}, args: {args}, kwargs: {kwargs}")
        # Crucially, call super().__new__ to actually create the instance
        instance = super().__new__(cls)
        print(f"CustomObject __new__ returning instance: {id(instance)}")
        return instance

    def __init__(self, name, id):
        print(f"CustomObject __init__ called. self: {id(self)}, name: {name}, id: {id}")
        self.name = name
        self.id = id
        print(f"CustomObject __init__ finished setting attributes.")

co = CustomObject("Test", 123)
print(f"co.name: {co.name}, co.id: {co.id}")


# --- Example 3: Implementing a Singleton using __new__ ---
print("\n--- Implementing a Singleton using __new__ ---")

class Singleton:
    _instance = None # Class-level attribute to hold the single instance

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            print("Singleton __new__: Creating the single instance.")
            cls._instance = super().__new__(cls)
        else:
            print("Singleton __new__: Returning existing instance.")
        return cls._instance

    def __init__(self, value):
        # __init__ will be called every time Singleton() is invoked,
        # but only the first call should initialize the state if you want
        # true singleton behavior.
        if not hasattr(self, '_initialized'): # Prevent re-initialization
            print(f"Singleton __init__: Initializing with value: {value}")
            self.value = value
            self._initialized = True
        else:
            print(f"Singleton __init__: Already initialized. Ignoring new value: {value}")

s1 = Singleton("First value")
s2 = Singleton("Second value") # __init__ still gets called, but should not re-initialize

print(f"s1 is s2: {s1 is s2}") # True, they are the same instance
print(f"s1.value: {s1.value}") # Should be "First value"
print(f"s2.value: {s2.value}") # Should also be "First value"

s3 = Singleton("Third value")
print(f"s3.value: {s3.value}")


# --- Example 4: Subclassing an immutable built-in type (MUST use __new__) ---
print("\n--- Subclassing Immutable Built-in (MUST use __new__) ---")

class MyInt(int): # Inheriting from int (an immutable type)
    def __new__(cls, value, extra_data=None):
        print(f"MyInt __new__ called with value: {value}")
        # Must call super().__new__ and pass the 'value' to create the int object
        instance = super().__new__(cls, value)
        instance.extra_data = extra_data # This attribute is set on the newly created int instance
        print(f"MyInt __new__ returning instance: {instance} (with extra data: {instance.extra_data})")
        return instance

    def __init__(self, value, extra_data=None):
        # __init__ is called, but it cannot modify the 'int' value itself.
        # It's only for setting up additional instance attributes.
        print(f"MyInt __init__ called. self value: {self}, self extra_data: {self.extra_data}")
        # If you tried to do self = value here, it would be an error because 'self' is immutable.
        # self.value = value # This is WRONG for immutable types.

my_num = MyInt(100, "hundred")
print(f"my_num: {my_num}")
print(f"type(my_num): {type(my_num)}")
print(f"my_num + 5: {my_num + 5}") # Behaves like an int
print(f"my_num.extra_data: {my_num.extra_data}")

# If you tried to just use __init__ for MyInt:
# class BadInt(int):
#     def __init__(self, value):
#         self = value # This would be a TypeError: 'int' object has no attribute '__dict__'
#         self.extra_data = "bad"
#
# try:
#     bad_num = BadInt(5)
# except TypeError as e:
#     print(f"\nCaught expected TypeError for BadInt (showing why __new__ is needed): {e}")
        

Explanation of the Example Code:

  • **Basic Class with `__init__`:**
    • This is the standard, everyday usage. `__init__` is called, and it directly assigns the `value` to `self.value`. No `__new__` is explicitly defined, so Python's default `object.__new__` handles the object creation.
  • **Overriding `__new__` and `__init__`:**
    • The `CustomObject` class explicitly defines both `__new__` and `__init__`.
    • When `CustomObject("Test", 123)` is called, `CustomObject.__new__` runs first. It prints debugging info, then calls `super().__new__(cls)` to get the actual, empty instance. It then returns this instance.
    • Immediately after `__new__` returns an instance, `CustomObject.__init__` is called, receiving that same instance (`self`) and the original arguments. It then initializes the instance's attributes.
    • The `id()` calls help confirm that `__new__` creates the object, and `__init__` receives and initializes that *same* object.
  • **Implementing a Singleton using `__new__`:**
    • The `Singleton` class ensures that only one instance of itself is ever created.
    • `__new__` checks `cls._instance`. If it's `None`, it creates the first (and only) instance using `super().__new__` and stores it in `cls._instance`. Subsequent calls to `Singleton()` will bypass the creation step and simply return the existing `_instance`.
    • The `__init__` method has a guard (`if not hasattr(self, '_initialized')`) to prevent it from re-initializing the singleton's state every time the class is "instantiated" (i.e., every time `Singleton()` is called, even if `__new__` returns an existing object). This is crucial for a robust singleton pattern.
  • **Subclassing Immutable Built-in Type (`MyInt`):**
    • This is a critical use case for `__new__`. When you subclass an immutable type like `int`, you *must* override `__new__` because `__init__` cannot modify the immutable value of the `int` object itself.
    • `MyInt.__new__` takes `value` and passes it to `super().__new__(cls, value)`. This step is what actually creates the `int` object with the specified `value`.
    • Any additional attributes (like `extra_data`) must be set on the `instance` *within* `__new__` before it's returned.
    • The `__init__` method for `MyInt` *is* called, but it's primarily for any *additional* setup that doesn't involve altering the core immutable value (which is already set by `__new__`). Trying to assign to `self` in `__init__` for an immutable type will raise a `TypeError`.

These examples provide a comprehensive illustration of when and why to use `__new__` in conjunction with (or instead of) `__init__`, particularly for specialized object creation patterns.