Python

Python __new__ vs __init__: Understanding Object Creation

Learn the difference between Python's __new__ and __init__ methods: __new__ creates the raw object, while __init__ initializes it. Discover when to use each, with practical patterns like singletons and caching.

July 2026 8 min read 12 views 0 hearts

The Two Faces of Object Creation in Python

When you write my_object = MyClass() in Python, you're actually triggering a two-step process that most developers never think about. The first step creates the object itself. The second step initializes it with your data. These two steps are handled by __new__ and __init__ respectively, and understanding the difference between them can fundamentally change how you think about Python's object model.

Let's be honest here - most Python developers spend their entire careers only using __init__ and never touching __new__. And that's perfectly fine for 95% of the code you'll write. But that remaining 5%? That's where __new__ becomes your secret weapon.

What Actually Happens When You Create an Object

Here's something PythonSkillset users often find surprising: __init__ is not a constructor in the traditional sense. The real constructor is __new__. Think of it this way:

  • __new__ creates the raw object (like ordering an empty shell from a factory)
  • __init__ fills that shell with data (like furnishing your new apartment)
class MyClass:
    def __new__(cls, *args, **kwargs):
        print("1. Creating the object")
        instance = super().__new__(cls)
        return instance

    def __init__(self, value):
        print("2. Initializing the object")
        self.value = value

obj = MyClass(42)
# Output:
# 1. Creating the object
# 2. Initializing the object

Notice that __new__ receives the class itself as the first argument, not the instance. That's because the instance doesn't exist yet - __new__ is about to create it.

Why You'd Ever Need new

Most of the time, you don't. But here's where things get interesting. __new__ gives you control over object creation itself, before any initialization happens. This opens up some powerful patterns.

Singleton Pattern Made Simple

class DatabaseConnection:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, connection_string):
        # This runs every time, even if returning existing instance
        if not hasattr(self, 'initialized'):
            self.connection = connection_string
            self.initialized = True

db1 = DatabaseConnection("server1")
db2 = DatabaseConnection("server2")
print(db1 is db2)  # True - same instance
print(db1.connection)  # "server1" - first connection preserved

Returning Different Objects

Here's something that __init__ can never do - return a completely different object type:

class StringOrInt:
    def __new__(cls, value):
        if isinstance(value, str):
            return super().__new__(cls)
        return int(value)  # Returns an integer instead!

obj1 = StringOrInt("hello")
obj2 = StringOrInt(42)
print(type(obj1))  # <class '__main__.StringOrInt'>
print(type(obj2))  # <class 'int'>

Immutable Objects and init

This is a classic gotcha. For immutable types like tuples and strings, __init__ runs after the object is already created - but you can't modify it:

class ImmutableTuple(tuple):
    def __new__(cls, *args):
        # Must set attributes here, not in __init__
        instance = super().__new__(cls, *args)
        instance.created_at = "now"  # This works
        return instance

    def __init__(self, *args):
        # This runs but can't modify the tuple itself
        super().__init__()

t = ImmutableTuple([1, 2, 3])
print(t.created_at)  # "now"

The Order of Operations Matters

Here's something that trips up even experienced PythonSkillset readers. When you subclass and override both methods, the order is always:

  1. __new__ (creates object)
  2. __init__ (initializes if __new__ returned an instance of the same class)

But here's the catch - if __new__ returns an object of a different class, __init__ never runs:

class ReturnDifferent:
    def __new__(cls, return_int=False):
        if return_int:
            return 42  # __init__ WON'T be called
        return super().__new__(cls)

    def __init__(self):
        print("__init__ called")

obj1 = ReturnDifferent(return_int=True)
print(type(obj1))  # <class 'int'> - no __init__ called

obj2 = ReturnDifferent(return_int=False)
print(type(obj2))  # <class '__main__.ReturnDifferent'> - __init__ called

When to Use Each One

Here's my practical advice after years of Python development:

Use init when you:

  • Need to set instance attributes
  • Want to validate or transform input data
  • Are building regular classes for everyday use

Use new when you:

  • Need to control object creation (singletons, factories)
  • Are subclassing immutable types
  • Want to intercept the creation process before initialization

The vast majority of your code should only use __init__. __new__ is a specialized tool for specific situations - and that's exactly how it should be used.

Real World Example: Caching Object Creation

Here's a pattern I've used in production at PythonSkillset. We had a system creating thousands of similar objects and wanted to cache the creation:

class CachedObject:
    _cache = {}

    def __new__(cls, identifier, *args, **kwargs):
        if identifier in cls._cache:
            return cls._cache[identifier]

        instance = super().__new__(cls)
        cls._cache[identifier] = instance
        return instance

    def __init__(self, identifier, data):
        if not hasattr(self, 'initialized'):
            self.data = data
            self.initialized = True

# First call creates the object
obj1 = CachedObject("user_1", {"name": "Alice"})
# Second call returns cached object - __init__ still runs but we skip it
obj2 = CachedObject("user_1", {"name": "Bob"})
print(obj1 is obj2)  # True
print(obj1.data)  # {"name": "Alice"} - cached data preserved

The Bottom Line

Understanding __new__ and __init__ isn't about using both all the time. It's about knowing the tools in your Python toolbox. When you encounter a problem that seems impossible with just __init__ - like creating singletons, working with immutable objects, or controlling instance creation - remember that __new__ is there for exactly those situations.

Most of the time, __init__ is all you need. But when you need __new__, nothing else will do. And that's the beautiful thing about Python - every layer of the language is accessible when you need it.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.