Skip to main content

__init__ vs __new__: What Actually Happens When You Create an Object

Advanced25 min5 exercises100 XP
0/5 exercises

When you write obj = MyClass(), it feels like one simple step. But under the hood, Python actually does two things: it creates the object, and then it initializes it. These are handled by two different methods — __new__ and __init__.

Think of it like building a house. __new__ is the construction crew that pours the foundation and puts up the walls — it creates the empty structure. __init__ is the interior designer who comes in afterward and sets up the furniture, paints the walls, and makes it livable.

Most of the time, you only need __init__. But understanding __new__ unlocks powerful patterns like singletons, caching, and customizing immutable types.

What Happens Step by Step When You Call MyClass()?

Here's the exact sequence Python follows when you write obj = MyClass(arg1, arg2):

  • Python calls MyClass.__new__(MyClass, arg1, arg2) to create a new instance
  • If __new__ returns an instance of MyClass, Python then calls instance.__init__(arg1, arg2) to initialize it
  • The initialized instance is assigned to obj
  • Watching the creation flow
    Loading editor...

    Notice the order: __new__ runs first and returns a raw, empty object. Then __init__ runs on that object and fills in the attributes. By the time you get the object back, both steps are done.

    How Does __new__ Work?

    __new__ is a static method (technically, it's special-cased by Python). It receives the class as its first argument (cls, not self) and must return an instance. The critical thing: __new__ must call super().__new__(cls) to actually allocate the object in memory.

    __new__ receives cls, not self
    Loading editor...

    A key rule: both __new__ and __init__ receive the same arguments (besides cls/self). Python passes whatever you put in MyClass(...) to both methods.

    When Do You Actually Need __new__?

    Most classes only need __init__. You need __new__ in three specific situations:

  • Subclassing immutable typesint, str, tuple, and frozenset can't be changed after creation, so __init__ is too late
  • Singleton pattern — returning the same instance every time
  • Instance caching — reusing previously created instances
  • Subclassing an immutable type
    Loading editor...

    With str, the string value is baked in at creation time. By the time __init__ runs, the string content is already frozen. That's why we must use __new__ to pass the uppercased value to super().__new__().

    Singleton pattern with __new__
    Loading editor...

    Can You Cache and Reuse Instances?

    A more flexible pattern than singleton is instance caching. Instead of forcing one instance, you reuse instances based on their arguments:

    Connection pooling with __new__
    Loading editor...

    The _initialized flag solves the problem of __init__ running repeatedly. On the first creation, attributes are set. On subsequent calls with the same arguments, __init__ sees the flag and skips re-initialization.

    What Are Common Mistakes with __new__?

    There are a few traps developers fall into when working with __new__. Let's look at the most common ones:

    Wrong: Setting attributes in __new__
    class Bad:
        def __new__(cls, x):
            self = super().__new__(cls)
            self.x = x  # Works, but wrong place
            return self
        # No __init__ — attributes set too early
    Right: Use __init__ for attributes
    class Good:
        def __new__(cls, x):
            return super().__new__(cls)
        
        def __init__(self, x):
            self.x = x  # Correct place

    While setting attributes in __new__ technically works, it breaks convention and can cause confusion. The rule of thumb: __new__ handles creation, __init__ handles initialization.


    Practice Exercises

    Predict the Creation Order
    Predict Output

    What does the following code print?

    class Widget:
        def __new__(cls, name):
            print(f'new: {name}')
            instance = super().__new__(cls)
            return instance
        
        def __init__(self, name):
            print(f'init: {name}')
            self.name = name
    
    w = Widget('button')
    print(w.name)
    Loading editor...
    Subclass an Immutable Type
    Write Code

    Create a class ClampedInt that subclasses int. It should clamp values to be between 0 and 255 (inclusive). Values below 0 become 0, values above 255 become 255.

    Since int is immutable, you must use __new__ to set the value.

    Print the results for ClampedInt(-50), ClampedInt(100), and ClampedInt(999).

    Loading editor...
    Build a Singleton
    Write Code

    Create a Logger class that implements the singleton pattern using __new__. No matter how many times you call Logger(), it should always return the same instance.

    Add an __init__ that sets self.logs to an empty list only on the *first* initialization (use a guard flag).

    Add a log(self, message) method that appends the message to self.logs.

    Create two Logger instances, log a message on each, then print the length of logs from the first instance.

    Loading editor...
    Fix the Immutable Subclass
    Fix the Bug

    This code tries to create a NamedTuple-like class by subclassing tuple, but the values aren't being set correctly. Fix it.

    Expected output:

    (10, 20)
    10
    20
    Loading editor...
    Build an Instance Cache
    Write Code

    Create a class Symbol that caches instances by name. If you call Symbol('x') twice, you should get the exact same object back. Use __new__ with a class-level dictionary for caching.

    Add a guard in __init__ to prevent re-initialization.

    Test it: create a = Symbol('x'), b = Symbol('x'), and c = Symbol('y'). Print whether a is b and whether a is c.

    Loading editor...