28.11 Object Creation: __new__ and __init__
In Python, object construction is a two-step process handled by the __new__ and __init__ methods. Understanding their distinct roles and interplay is fundamental to mastering the Python data model. The __new__ method is responsible for creating a new instance, while the __init__ method is responsible for initializing it. This separation provides powerful control over object instantiation, enabling patterns like immutable objects, singletons, and subclassing built-in types.
The Role of new
The __new__ method is a static method (though it doesn’t require the @staticmethod decorator) that takes the class of which an instance is requested as its first argument (cls). Its job is to allocate memory for the object and return a new instance. This instance is then passed as the self argument to __init__. If __new__ does not return an instance of cls (or a subclass), then the __init__ method of the new object will not be invoked. This is the mechanism that allows __new__ to control whether initialization occurs.
class Example:
def __new__(cls, *args, **kwargs):
print(f"__new__ called with {cls}, {args}, {kwargs}")
instance = super().__new__(cls) # Creates the actual instance
return instance # This return value is passed to __init__ as 'self'
def __init__(self, value):
print(f"__init__ called with {self}, {value}")
self.value = value
obj = Example(42)
# Output:
# __new__ called with <class '__main__.Example'>, (42,), {}
# __init__ called with <__main__.Example object at 0x...>, 42
The Role of init
The __init__ method is the initializer. It receives the object created by __new__ (as self) and any arguments used in the call to the class constructor. Its purpose is to set the initial state of the object by assigning values to its attributes. Crucially, __init__ does not return a value; its return value is ignored by Python. Its sole purpose is to mutate the new object.
Controlling Object Creation with new
The true power of __new__ lies in its ability to return an object that is not a new instance of the class, or to prevent the creation of a new instance altogether. This enables patterns like singletons or object caching.
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
a = Singleton()
b = Singleton()
print(a is b) # Output: True
Another common use case is creating subclasses of immutable types, such as tuple or int. Since their value is set at creation and cannot be changed by __init__, you must use __new__ to set the initial state.
class PositiveInteger(int):
def __new__(cls, value):
# We must use __new__ to create the new int object with our modified value
value = abs(value)
return super().__new__(cls, value)
num = PositiveInteger(-5)
print(num) # Output: 5
print(isinstance(num, int)) # Output: True
Common Pitfalls and Best Practices
A significant pitfall is forgetting to return an instance from __new__. If __new__ returns None or anything that isn’t an object, Python will fail.
class BadExample:
def __new__(cls):
print("Creating instance... but not returning it!")
# Implicitly returns None
def __init__(self):
print("This will never be called.")
try:
obj = BadExample() # TypeError: __init__() should return None, not 'NoneType'
except TypeError as e:
print(e)
When overriding __new__ in a class hierarchy, it is a best practice to call the superclass’s __new__ method using super().__new__(cls, ...). This ensures the proper chain of object creation is followed. Failing to do so breaks inheritance. Furthermore, the arguments accepted by __new__ should be compatible with those of __init__ and the base class’s __new__ method, as they are all called with the same set of arguments from the class constructor.
It is also critical to understand that if __new__ returns an instance of a different class, __init__ will not be called on the original class, but on the class of the returned object (if it has one). This behavior is the basis for metaclasses and factories.
class AlternateClass:
def __init__(self, x):
self.x = x * 2
class Factory:
def __new__(cls, value):
# Return an instance of a different class
return AlternateClass(value)
obj = Factory(10)
print(type(obj)) # Output: <class '__main__.AlternateClass'>
print(obj.x) # Output: 20
# The __init__ method of Factory was never called.