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.
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:
__new__(creates object)__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.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.