What Really Creates Python Objects? Unveiling __new__ vs __init__
This article explains how Python actually creates objects, clarifying the distinct roles of the __new__ and __init__ methods, and demonstrates practical techniques such as inheriting immutable types, adding metadata, and implementing singleton patterns through custom object creation.
Ever wondered whether __init__ is a constructor? In Python, object creation is split into two steps: __new__ allocates memory and returns a new instance, while __init__ initializes that instance’s state.
1. Theory: Object creation in Python
Python creates objects internally before calling the initializer. Understanding this mechanism lets you manipulate object creation for advanced use‑cases.
How to create objects in Python?
You simply instantiate a class, e.g. SimpleObject(name="bob"), or create built‑in types like str or int. The following class defines an __init__ method and a say_hello method:
class SimpleObject:
greet_name: str
def __init__(self, name: str):
self.greet_name = name
def say_hello(self) -> None:
print(f"Hello {self.greet_name}!")
my_instance = SimpleObject(name="bob")
my_instance.say_hello()Note that __init__ receives the name argument and stores it in the greet_name attribute, preserving the instance’s state.
__init__ is a constructor?
Technically, no. A constructor creates the object; __init__ only sets its state after the object has already been created.
What does __new__ do?
__new__is a static method called on the class itself. It must return an instance of the class and is responsible for the actual memory allocation.
Where does __new__ come from?
Everything in Python is an object, and the base object class defines __new__. Even a class that does not explicitly inherit anything is still an object subclass.
my_instance = SimpleObject(name="bob")
print(isinstance(my_instance, object)) # True
print(isinstance(42, object)) # True
print(isinstance('hello', object)) # True
print(isinstance({"my": "dict"}, object)) # TrueDifference between __new__ and __init__
__new__creates the object (allocates memory and returns it). Once the object exists, __init__ runs to initialize its attributes.
Python's object creation process
When a new object is created, Python executes the following functions in order: __new__: allocate memory and return a new object __init__: initialize the newly created object
Overriding __new__ lets you intervene in this process:
class SimpleObject:
greet_name: str
def __new__(cls, *args, **kwargs):
print("__new__ method")
return super().__new__(cls)
def __init__(self, name: str):
print("__init__ method")
self.greet_name = name
def say_hello(self) -> None:
print(f"Hello {self.greet_name}!")
my_instance = SimpleObject(name="bob")
my_instance.say_hello()Running this code prints:
__new__ method
__init__ method
Hello bob!The output confirms that __new__ runs before __init__. The default __new__ simply returns super().__new__(cls):
class SimpleObject:
def __new__(cls, *args, **kwargs):
return super().__new__(cls)Practical Application 1: Inheriting immutable types
By overriding __new__, you can inherit from immutable built‑ins like tuple. The following Point class stores coordinates in a tuple while adding custom methods and validation:
class Point(tuple):
x: float
y: float
def __new__(cls, x: float, y: float):
if x < 0 or y < 0:
raise ValueError("x and y must be positive")
return super().__new__(cls, (x, y))
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def distance_from(self, other_point: 'Point'):
import math
return math.sqrt((other_point.x - self.x) ** 2 + (other_point.y - self.y) ** 2)
p = Point(1, 2)
p2 = Point(3, 1)
print(p.distance_from(p2)) # 2.23606797749979Practical Application 2: Adding metadata
You can derive from float and attach extra information, such as a currency symbol:
class Currency(float):
def __new__(cls, value: float, symbol: str):
obj = super(Currency, cls).__new__(cls, value)
obj.symbol = symbol
return obj
def __str__(self) -> str:
return f"{self.symbol}{self:.2f}"
price = Currency(12.768544, symbol='€')
print(price) # €12.77
print(isinstance(price, float)) # True
print(f"{price.symbol}{price * 2}") # €25.54Practical Application 3: Singleton pattern
Using __new__ you can ensure a class has only one instance:
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 is singleton2) # TrueOther practical uses
Control instance creation : Add validation, logging, or modification before an object is created.
Factory methods : Decide which subclass to instantiate inside __new__.
Caching : Return a previously created object instead of allocating a new one.
Conclusion
This article explored Python’s object creation pipeline, clarified the distinct responsibilities of __new__ and __init__, and demonstrated how overriding __new__ enables advanced patterns such as immutable inheritance, metadata enrichment, and singletons, leading to more efficient and expressive code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
