Python

Python __new__ vs __init__: The Simple Difference

Understand the key difference between Python's __new__ and __init__ methods: __new__ creates objects, __init__ initializes them. Learn when to use each with real code examples.

August 2026 5 min read 10 views 0 hearts

Python's __new__ vs __init__: The Real Difference That Matters

If you've been writing Python for a while, you've probably used __init__ countless times. But there's another special method that often gets overlooked—__new__. When I first started coding in Python, I'll admit I treated __new__ as some mysterious dark art. But once I understood it, everything clicked.

Let me break down the difference in the simplest way possible.

The Short Answer

__init__ initializes an object that already exists.

__new__ creates the object in the first place.

Think of it like building a house. __new__ is the construction crew that pours the foundation and raises the walls. __init__ is the interior designer who arranges the furniture and chooses the paint color.

How They Work Together

When you write something like my_instance = MyClass(), Python does two things behind the scenes:

  1. First, it calls __new__ to allocate memory and create the object.
  2. Then, it calls __init__ to set up the object's initial state.

Here's a concrete example from a real project I worked on at PythonSkillset. We were building a configuration validator that needed to ensure every instance was unique:

class ConfigValidator:
    _instances = {}

    def __new__(cls, config_name):
        if config_name in cls._instances:
            return cls._instances[config_name]
        instance = super().__new__(cls)
        cls._instances[config_name] = instance
        return instance

    def __init__(self, config_name):
        self.config_name = config_name
        self.rules = []

Notice how __new__ checks if we already have a validator for that config name. If we do, it returns the existing one. This is the Singleton pattern in action, and you can't do it with just __init__.

When You Actually Need __new__

Most of the time, you won't need __new__. But here are the situations where it's indispensable:

1. Creating immutable objects Immutable types like tuple or int have their data set at creation time. You can't modify them in __init__ because they're already frozen.

class ImmutablePoint:
    def __new__(cls, x, y):
        instance = super().__new__(cls)
        instance._x = x
        instance._y = y
        return instance

    def __init__(self, x, y):
        # No initialization needed here
        pass

    @property
    def x(self):
        return self._x

2. Controlling instance creation Like our ConfigValidator example above. Sometimes you want to return existing instances or create subclasses automatically.

3. Metaclasses and frameworks Libraries like SQLAlchemy and Django use __new__ extensively for things like model creation and serialization.

The Gotchas I Learned the Hard Way

When I was writing the PythonSkillset guild database module, I ran into a subtle issue:

class User:
    def __new__(cls, user_id, name):
        instance = super().__new__(cls)
        instance.user_id = user_id
        return instance

    def __init__(self, user_id, name):
        self.name = name  # This overwrites what __new__ set

See the problem? I set user_id in __new__, but then __init__ didn't touch it. But if I had set self.name in both places, the __init__ value would win. The key rule is: __init__ runs after __new__, and it will overwrite any attributes you set in __new__.

The Only Rule You Need

Here's a simple guideline I use at PythonSkillset:

  • Use __init__ for 99% of your code. It's simpler and what people expect.
  • Use __new__ only when you need to control object creation itself, not just initialization.

If you find yourself writing __new__ for anything other than singletons, immutable objects, or framework internals, step back and ask yourself if there's a cleaner way.

A Quick Test

To see the order of execution yourself, run this:

class Test:
    def __new__(cls):
        print("1. __new__ called")
        return super().__new__(cls)

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

t = Test()
# Output:
# 1. __new__ called
# 2. __init__ called

The beauty of Python is that these details aren't just theoretical—they matter in real code. Understanding the difference between creation and initialization has saved me countless hours debugging strange behavior in production.

Next time you run into an issue where an object isn't behaving as expected, remember: it might not be about how it's initialized, but how it was created in the first place.

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.