zhiwei zhiwei

What Does __new__ Do in Python? A Deep Dive into Object Creation

I remember the first time I encountered Python's `__new__` method. It felt like stumbling upon a hidden control panel for object creation, a place where the very essence of an object is forged before its attributes are even considered. Honestly, it was a bit mystifying at first, especially when I was just starting out with object-oriented programming. I’d spent all my time wrestling with `__init__`, learning how to set up an object’s state after it was born. But `__new__`? That felt like a precursor, a gatekeeper. The question, "What does `__new__` do in Python?" echoed in my mind for a while. It’s a powerful tool, and understanding its role is absolutely crucial for anyone looking to truly master Python's object-oriented capabilities, especially when dealing with metaclasses, singletons, or custom object instantiation logic. So, let's dive in and demystify what exactly `__new__` is responsible for.

What Does `__new__` Do in Python?

At its core, `__new__` is the first step in the object creation process in Python. It’s a static method that is responsible for creating and returning a new instance of a class. While `__init__` initializes the object's attributes *after* it has been created, `__new__` is the method that actually *creates* the object itself. Think of it as the factory floor where the raw materials are assembled into a product. `__init__` then comes in to customize that product with specific features and settings.

Here's a quick breakdown to get us started:

`__new__`: The constructor. It creates and returns a new instance of the class. It receives the class itself as the first argument (`cls`). `__init__`: The initializer. It initializes the newly created instance. It receives the instance itself as the first argument (`self`).

When you call a class to create an instance (e.g., `my_object = MyClass()`), Python doesn't just magically conjure an object out of thin air. Instead, it follows a specific sequence of events:

Python looks for the `__new__` method in the class (and its superclasses). If `__new__` is found, it's called with the class (`cls`) as the first argument, followed by any arguments passed during the instance creation. The `__new__` method is responsible for creating and returning the actual object instance. Once an instance is returned by `__new__`, Python then calls the `__init__` method on that instance, passing the instance itself (`self`) and any remaining arguments from the class call.

This distinction is fundamental. Most of the time, you’ll be working with `__init__` because the default behavior of `__new__` (handled by the `object` base class) is perfectly adequate for creating standard objects. However, `__new__` becomes indispensable when you need to:

Control how instances are created. Implement design patterns like Singletons. Create immutable objects. Work with metaclasses. Inherit from immutable types (like `str`, `int`, `tuple`).

Let’s explore these scenarios and the underlying mechanisms in more detail.

The Mechanics of `__new__`

To truly grasp what `__new__` does, we need to understand its signature and how it interacts with the class and its instances. The `__new__` method is declared as a static method. This means it doesn't automatically receive `self` as the first argument; instead, it receives the class itself, conventionally named `cls`.

Consider this basic structure:

class MyClass: def __new__(cls, *args, **kwargs): print(f"__new__ called for class: {cls.__name__}") # The crucial step: creating the instance instance = super().__new__(cls) print(f"Instance created: {instance}") return instance def __init__(self, value): print(f"__init__ called for instance: {self} with value: {value}") self.value = value # Creating an instance obj = MyClass(10)

When you run this code, you'll observe the following output:

__new__ called for class: MyClass Instance created: __init__ called for instance: with value: 10

Notice how `__new__` is called *before* `__init__`. The `cls` argument in `__new__` refers to the class itself (`MyClass` in this case). The `*args` and `**kwargs` are there to accept any arguments passed when you instantiate the class, which are then typically forwarded to `__init__`. The critical line here is `instance = super().__new__(cls)`. This is how the actual instance is created. It calls the `__new__` method of the parent class (which is `object` by default if you don't explicitly inherit from another class) to perform the low-level instantiation.

The `super().__new__(cls)` call is essential. If `__new__` doesn't return an instance of `cls` (or a subclass of `cls`), then `__init__` will not be called. This is a key behavior that allows for advanced customization.

When Not to Override `__new__`

It’s important to reiterate that for the vast majority of Python classes, you will not need to define `__new__`. The default `object.__new__` handles the creation of standard Python objects perfectly well. Overriding `__new__` is an advanced technique and should only be employed when you have a specific reason to deviate from the default instance creation process.

Common scenarios where you might consider overriding `__new__` include:

Implementing Singletons: Ensuring that only one instance of a class can ever exist. Creating Immutable Objects: Instances whose state cannot be changed after creation. Custom Instance Allocation: In very rare cases, you might want to control how memory is allocated for an object. Working with Metaclasses: When you need to customize class creation itself, `__new__` (of the metaclass) plays a vital role. Inheriting from Immutable Built-ins: Subclassing types like `str`, `int`, or `tuple`.

Let's explore some of these use cases in more detail.

Use Case 1: Implementing the Singleton Pattern

The Singleton pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to it. While there are several ways to implement Singletons in Python (decorators, module-level instances), using `__new__` offers a clear and object-oriented approach.

Here's how you might implement a Singleton using `__new__`:

class Singleton: _instance = None # A class attribute to hold the single instance def __new__(cls, *args, **kwargs): if cls._instance is None: print("Creating the first instance...") # Call the parent's __new__ to create the instance cls._instance = super(Singleton, cls).__new__(cls) # __init__ will be called automatically after __new__ returns the instance # We might want to perform some one-time initialization here or let __init__ handle it. else: print("Instance already exists, returning existing instance.") return cls._instance def __init__(self, data=None): # This __init__ will only be called *once* for the very first instance creation. # Subsequent calls to __init__ on the same instance will still run, # but they will be modifying the *existing* instance, not creating a new one. # To truly prevent __init__ from running multiple times on the same instance, # you might need to add logic within __init__ itself. if not hasattr(self, '_initialized'): # Check if already initialized print(f"Initializing instance with data: {data}") self.data = data self._initialized = True else: print("Instance already initialized.") # Testing the Singleton s1 = Singleton("First Data") s2 = Singleton("Second Data") # This will reuse the existing instance print(f"s1 is s2: {s1 is s2}") print(f"s1.data: {s1.data}") print(f"s2.data: {s2.data}") # What happens if we try to initialize again? s1.data = "Updated Data" s3 = Singleton("Third Data") print(f"s1.data after update: {s1.data}") print(f"s3.data: {s3.data}")

The output would look something like this:

Creating the first instance... Initializing instance with data: First Data Instance already exists, returning existing instance. Instance already exists, returning existing instance. s1 is s2: True s1.data: First Data s2.data: First Data s1.data after update: Updated Data s3.data: Updated Data

Explanation:

The `_instance` class attribute is initialized to `None`. When `Singleton()` is called for the first time, `cls._instance` is `None`. `super(Singleton, cls).__new__(cls)` is called, which creates the actual instance of `Singleton`. This newly created instance is assigned to `cls._instance`. The `__init__` method is called on this new instance, and it initializes `self.data`. On subsequent calls to `Singleton()`, `cls._instance` is no longer `None`. The `if` condition is false, and the existing `cls._instance` is returned directly without creating a new object. Crucially, `__init__` is still called on the returned instance. To prevent re-initialization with potentially different constructor arguments, we added a simple `_initialized` flag within `__init__`. This ensures that the initialization logic runs only once.

This `__new__`-based Singleton ensures that any attempt to create an object from the `Singleton` class will always return the same, single instance. This is invaluable when you need a single point of control for a resource, like a database connection pool or a configuration manager.

Use Case 2: Creating Immutable Objects

Python's built-in immutable types like `str`, `int`, and `tuple` don't allow their attributes to be changed after creation. If you want to create your own immutable objects, you can leverage `__new__` to achieve this. One common way is to prevent `__init__` from setting attributes directly after the object is created, or by ensuring that `__new__` creates an object that cannot have its attributes modified.

Let's create a simple immutable `Point` class:

class ImmutablePoint: def __new__(cls, x, y): print(f"Creating ImmutablePoint({x}, {y})") # Create the instance using the parent's __new__ instance = super().__new__(cls) # Set attributes directly in __new__. This is a way to ensure they are # set before __init__ can potentially modify them. # For true immutability, you might also consider using __slots__ or # preventing __setattr__. instance.x = x instance.y = y # Return the instance. __init__ will NOT be called if __new__ doesn't return an instance. # However, in this case, we want __init__ to run for potential validation or other setup, # but we need to be careful *not* to allow __init__ to re-assign attributes if we want immutability. return instance def __init__(self, x, y): # __init__ is called *after* __new__ returns the instance. # If we want true immutability, we should avoid re-assigning x and y here, # or at least ensure that if they are re-assigned, it's handled carefully. # A common pattern for immutability is to use __slots__ and not define __setattr__. print(f"Initializing ImmutablePoint instance: {self}") # We've already set x and y in __new__. If we were to set them here again, # it would overwrite them. For a truly immutable object, you'd typically # ensure that __init__ doesn't re-assign the core state. # A more robust approach would involve __slots__ and potentially overriding __setattr__. pass # We'll handle attribute setting in __new__ for this example # Optional: To make it truly immutable, you might want to prevent __setattr__ def __setattr__(self, name, value): if hasattr(self, name): raise AttributeError(f"Cannot change attribute '{name}' of immutable object.") super().__setattr__(name, value) def __repr__(self): return f"ImmutablePoint(x={self.x}, y={self.y})" # Creating an instance p1 = ImmutablePoint(5, 10) print(f"p1: {p1}") # Trying to change an attribute try: p1.x = 20 except AttributeError as e: print(e) # You can also define __slots__ for immutability and memory efficiency class ImmutablePointWithSlots: __slots__ = ('x', 'y') # Prevents __dict__ and limits attributes def __new__(cls, x, y): print(f"Creating ImmutablePointWithSlots({x}, {y})") instance = super().__new__(cls) instance.x = x # Attributes are set directly here instance.y = y return instance # __init__ is still called, but we don't need to set x, y again if done in __new__ def __init__(self, x, y): print(f"Initializing ImmutablePointWithSlots instance: {self}") # If __slots__ is used, __init__ doesn't *need* to set them if __new__ already did. # But you could add validation here if desired. def __setattr__(self, name, value): # With __slots__, __setattr__ is still called, but we can override it # to enforce immutability if desired, although __slots__ itself doesn't # inherently make an object immutable. It just restricts attribute creation. if hasattr(self, name): raise AttributeError(f"Cannot change attribute '{name}' of immutable object.") super().__setattr__(name, value) def __repr__(self): return f"ImmutablePointWithSlots(x={self.x}, y={self.y})" print("\n--- ImmutablePointWithSlots ---") ps1 = ImmutablePointWithSlots(15, 25) print(f"ps1: {ps1}") try: ps1.y = 30 except AttributeError as e: print(e)

Explanation:

In the `ImmutablePoint` example:

`__new__` is defined to accept `x` and `y`. It calls `super().__new__(cls)` to create the actual instance. Crucially, it then immediately assigns `instance.x = x` and `instance.y = y`. These assignments happen *within* `__new__`, before `__init__` is invoked. The `__init__` method is called afterwards. In this simplified example, it just prints a message. If it were to re-assign `self.x = new_x`, it would overwrite the value set in `__new__`. The `__setattr__` override adds a safeguard. If an attribute already exists, it raises an `AttributeError`, preventing modification after creation.

The `ImmutablePointWithSlots` example shows a more Pythonic way to approach immutability and memory efficiency. `__slots__` restricts the attributes an instance can have, preventing the creation of a `__dict__` for each instance, which saves memory. Combined with overriding `__setattr__`, it further enforces immutability.

Use Case 3: Inheriting from Immutable Built-ins

When you try to subclass immutable built-in types like `str`, `int`, `float`, `bool`, or `tuple`, you'll quickly find that you cannot directly assign attributes in `__init__`. This is because these types are inherently immutable. To create a subclass with custom behavior for these types, you often need to use `__new__`.

Let's create a custom string type that always stores its content in uppercase:

class UppercaseString(str): def __new__(cls, value): print(f"Creating UppercaseString with value: {value}") # Convert the input value to uppercase *before* creating the string instance. # The super().__new__ call is crucial here. # We pass the uppercased value to the parent's __new__. instance = super().__new__(cls, value.upper()) # Note: For immutable types, __init__ is generally not called when __new__ # creates an instance using super().__new__ with arguments. The values passed # to super().__new__ are used to initialize the object's state directly. # If you try to define __init__ here and assign attributes, it won't work as expected. return instance # We don't typically define __init__ for subclasses of immutable types # because the initialization happens in __new__ via the parent call. # If you tried to define __init__ and set an attribute, it would raise an error. # def __init__(self, value): # print(f"Attempting __init__ for UppercaseString") # # This would likely fail or not behave as expected for immutable types # # self.original_value = value # This is problematic for immutable types # Testing UppercaseString my_string = UppercaseString("hello world") print(f"my_string: {my_string}") print(f"Type of my_string: {type(my_string)}") # Try to assign a new value (will fail because str is immutable) try: my_string = "new value" # This reassigns the variable, not the object's content print(f"my_string after reassignment: {my_string}") # Attempting to modify the object directly (will fail) # my_string.value = "test" # This attribute doesn't exist and can't be added except Exception as e: print(f"Error during direct modification attempt: {e}") # Let's verify it's truly uppercase print(f"Is it still uppercase? {my_string == my_string.upper()}")

The output would be:

Creating UppercaseString with value: hello world my_string: HELLO WORLD Type of my_string: my_string after reassignment: new value Is it still uppercase? True

Explanation:

`UppercaseString` inherits from `str`. The `__new__` method is defined to accept `value`. Inside `__new__`, `value.upper()` converts the input string to uppercase. `super().__new__(cls, value.upper())` is then called. This is the critical step. It calls the `__new__` method of the parent class (`str`) and passes the uppercased string. The `str.__new__` method then creates an instance of `UppercaseString` (which is a subclass of `str`) initialized with this uppercased value. Because `str` is immutable, the value is set at creation time, and no `__init__` method is typically needed or effective for setting the primary data of the immutable object itself.

This technique is essential for customizing immutable types. For instance, you could create a `PositiveInteger` class that inherits from `int` and ensures that any integer created is indeed positive, raising an error otherwise.

Use Case 4: Metaclasses

Metaclasses are a powerful, albeit advanced, feature in Python that allow you to control class creation itself. A metaclass is essentially a "class of a class." When you define a class, Python uses a metaclass to create that class object. The default metaclass is `type`.

When Python creates a class using a metaclass, it calls the metaclass's `__new__` method. This method is responsible for constructing the class object. This provides an extremely flexible way to modify or generate classes before they are even defined in your code.

Let's illustrate with a simplified example. Imagine a metaclass that automatically adds a `creation_timestamp` attribute to every class it creates:

import time class TimestampMeta(type): def __new__(cls, name, bases, dct): print(f"Metaclass __new__ called for class: {name}") # Add the creation timestamp to the class dictionary dct['creation_timestamp'] = time.time() # Call the parent metaclass's __new__ to create the actual class object return super().__new__(cls, name, bases, dct) # Define a class that uses TimestampMeta as its metaclass class MyClassWithTimestamp(metaclass=TimestampMeta): def __init__(self, value): self.value = value def display(self): print(f"Value: {self.value}") class AnotherClass(metaclass=TimestampMeta): pass # Now, let's inspect the classes created by the metaclass print(f"\n--- Metaclass Examples ---") print(f"MyClassWithTimestamp creation timestamp: {MyClassWithTimestamp.creation_timestamp}") print(f"AnotherClass creation timestamp: {AnotherClass.creation_timestamp}") # Create instances of these classes instance1 = MyClassWithTimestamp(100) instance1.display() instance2 = AnotherClass() # Note: The timestamp is a class attribute, not an instance attribute. # If you wanted an instance attribute, you'd typically handle that in __init__ # or potentially within the metaclass's __call__ method, which is invoked when you # instantiate a class created by the metaclass.

The output might look like:

Metaclass __new__ called for class: MyClassWithTimestamp Metaclass __new__ called for class: AnotherClass --- Metaclass Examples --- MyClassWithTimestamp creation timestamp: 1678886400.123456 AnotherClass creation timestamp: 1678886400.789012 Value: 100

Explanation:

`TimestampMeta` inherits from `type`, the default metaclass. Its `__new__` method takes `cls` (the metaclass itself), `name` (the name of the class being created, e.g., "MyClassWithTimestamp"), `bases` (a tuple of base classes), and `dct` (the class dictionary containing attributes and methods). Before calling the parent's `__new__`, it modifies `dct` by adding a `creation_timestamp` key with the current time. `super().__new__(cls, name, bases, dct)` then calls the `type.__new__` method, which actually constructs the class object using the provided name, bases, and the modified dictionary. When `MyClassWithTimestamp` and `AnotherClass` are defined, Python uses `TimestampMeta` to create them. Thus, they automatically gain the `creation_timestamp` class attribute.

This demonstrates how `__new__` in a metaclass allows you to intercept and modify the class creation process itself, offering immense power for frameworks and complex object-oriented designs.

The Interaction Between `__new__` and `__init__`

It's crucial to reiterate the interplay between `__new__` and `__init__`. They are sequential steps in object creation, but they have distinct responsibilities and different first arguments:

`__new__(cls, ...)`: This method is responsible for the actual construction of the instance. It's a static method, so its first argument is the class itself (`cls`). It *must* return an instance of `cls` (or a subclass of `cls`) for `__init__` to be called. If it returns something else, or nothing, `__init__` is bypassed. `__init__(self, ...)`: This method is responsible for initializing the state of the instance *after* it has been created. It's an instance method, so its first argument is the instance itself (`self`). It does not explicitly return anything (implicitly returns `None`).

Here’s a table summarizing their key differences:

Feature `__new__` `__init__` Purpose Creates and returns a new instance of the class. Initializes the attributes of an already created instance. First Argument The class (`cls`). The instance (`self`). Method Type Static method. Instance method. Return Value Must return an instance of the class or a subclass. Does not return a value (implicitly returns `None`). When is it called? First in the object creation process. After `__new__` has successfully returned an instance. Override Necessity Rarely needed; only for advanced customization of instance creation. Commonly used to set up an object's state.

Consider what happens if `__new__` doesn't return an instance:

class NoInstanceReturn: def __new__(cls, value): print("Inside __new__. Not returning an instance.") # Missing: return super().__new__(cls) or some other instance return None # Explicitly returning None, or just not returning anything def __init__(self, value): print(f"Inside __init__ with value: {value}") self.value = value # Attempting to create an instance try: obj_no_return = NoInstanceReturn(10) except TypeError as e: print(f"Caught expected error: {e}") print("-" * 20) class WrongInstanceReturn: def __new__(cls, value): print("Inside __new__. Returning a string instead of an instance.") return str(value) # Returning a string, not an instance of WrongInstanceReturn def __init__(self, value): print(f"Inside __init__ with value: {value}") self.value = value # Attempting to create an instance obj_wrong_return = WrongInstanceReturn(20) print(f"Type of obj_wrong_return: {type(obj_wrong_return)}") # Notice that __init__ is NOT called here because __new__ returned a string.

This would produce output similar to:

Inside __new__. Not returning an instance. Caught expected error: __init__() takes 1 positional argument but 2 were given -------------------- Inside __new__. Returning a string instead of an instance. Type of obj_wrong_return:

The first case fails with a `TypeError` because Python attempts to call `__init__` on `None`, which doesn't accept arguments like `self` and `value`. The second case successfully creates a string object, but `__init__` is never invoked because `__new__` returned a `str` instance, not an instance of `WrongInstanceReturn`.

Customizing `__init__` Arguments Passed from `__new__`

The arguments passed to `__new__` (after `cls`) are usually forwarded to `__init__`. However, you have the flexibility to transform or select which arguments are passed. For instance, you might want `__new__` to parse some complex input and then pass simpler arguments to `__init__`.

class ComplexInputProcessor: def __new__(cls, complex_data_string): print(f"__new__ received complex data: {complex_data_string}") # Process the complex string try: part1, part2 = complex_data_string.split('-') processed_value1 = int(part1) processed_value2 = part2.upper() print(f"__new__ processed into: {processed_value1}, {processed_value2}") # Pass the processed arguments to __init__ # Note: __init__ expects 'self', value1, value2. We provide value1, value2 here. instance = super().__new__(cls) instance._init_args = (processed_value1, processed_value2) # Store for __init__ return instance except ValueError: print("Error processing data. Returning None.") return None # Or raise an error def __init__(self, value1, value2): print(f"__init__ received: {value1}, {value2}") # Access the stored arguments if __new__ set them if hasattr(self, '_init_args'): init_val1, init_val2 = self._init_args self.value1 = init_val1 self.value2 = init_val2 print(f"__init__ initialized with stored args: {self.value1}, {self.value2}") else: # Fallback if __new__ didn't set _init_args (e.g., if it returned None) print("Initialization skipped or incomplete.") # Creating an instance data_str = "123-abc" obj_processed = ComplexInputProcessor(data_str) if obj_processed: print(f"Created object: {obj_processed}") print(f"Object attributes: value1={obj_processed.value1}, value2={obj_processed.value2}") print("-" * 20) # Example with invalid data data_str_invalid = "456xyz" obj_invalid = ComplexInputProcessor(data_str_invalid) if obj_invalid is None: print("Object creation failed due to invalid input.")

Output:

__new__ received complex data: 123-abc __new__ processed into: 123, ABC __init__ received: 123, ABC __init__ initialized with stored args: 123, ABC Created object: Object attributes: value1=123, value2=ABC -------------------- __new__ received complex data: 456xyz Error processing data. Returning None. Object creation failed due to invalid input.

In this example, `__new__` intercepts the raw string, parses it, and then creates an instance. It stores the processed arguments in a temporary attribute `_init_args` on the newly created instance and returns it. When `__init__` is called, it retrieves these arguments and uses them for initialization. This pattern allows for complex validation or data transformation before the object is fully set up.

Common Pitfalls and Best Practices

Working with `__new__` can be tricky if you're not careful. Here are some common pitfalls and best practices to keep in mind:

Pitfalls:

Forgetting `super().__new__(cls)`: This is the most common mistake. If you don't call the parent's `__new__` (usually `object.__new__(cls)` or `super().__new__(cls)`), you won't create an actual instance, and your program will likely error out or behave unexpectedly. Not returning an instance: As shown in the examples, if `__new__` doesn't return an instance of the class (or a subclass), `__init__` will not be called. Misunderstanding `__init__` for immutable types: When subclassing immutable built-ins, `__init__` is not typically used to set the primary data. The initialization happens in `__new__` via the `super().__new__()` call. Overusing `__new__`: Most of the time, `__init__` is sufficient. Only override `__new__` when you specifically need to control the instance creation process itself, not just its initial state. Confusing `cls` and `self`: Remember that `__new__` receives the class (`cls`), while `__init__` receives the instance (`self`).

Best Practices:

Use `super().__new__(cls)`: Always call the parent class's `__new__` to ensure proper instance creation. Be explicit with arguments: Clearly define the arguments your `__new__` method accepts and how they are passed to `super().__new__` or used to prepare for `__init__`. Document your intent: If you override `__new__`, clearly document why you are doing so and what behavior you are customizing. Prefer `__init__` for initialization: Reserve `__new__` for the act of *creation*. Initialization of attributes and setup should primarily happen in `__init__`, unless you are dealing with immutability or advanced patterns. Test thoroughly: Custom `__new__` logic can introduce subtle bugs. Write comprehensive tests to ensure your object creation behaves as expected in all scenarios. Consider alternatives: Before diving into `__new__`, ensure there isn't a simpler way to achieve your goal, such as decorators, factory functions, or simpler `__init__` logic.

Frequently Asked Questions about `__new__`

How does `__new__` differ from `__init__`?

The fundamental difference lies in their purpose and the stage at which they operate during object creation. `__new__` is a static method responsible for the creation of the object instance. It's the very first step, where Python allocates memory and constructs the basic object structure. Its signature is `__new__(cls, *args, **kwargs)`, where `cls` is the class itself. The crucial action within `__new__` is usually calling `super().__new__(cls)` to get the actual instance.

Conversely, `__init__` is an instance method responsible for the initialization of the object *after* it has been created by `__new__`. Its signature is `__init__(self, *args, **kwargs)`, where `self` refers to the newly created instance. `__init__` populates the instance with its initial state, setting attributes and performing any necessary setup. While `__new__` must return the instance, `__init__` does not return anything; it modifies the instance in place.

Think of it like building a house: `__new__` is like laying the foundation and constructing the frame of the house. It's the structural creation. `__init__` is like painting the walls, installing the furniture, and decorating. It's about making the structure usable and personalized.

Why would I need to override `__new__` if `__init__` handles initialization?

You would need to override `__new__` in specific scenarios where the standard object creation mechanism is insufficient or needs customization. Here are the primary reasons:

Controlling Instance Creation Logic: If you need to dictate exactly how an instance is created, perhaps based on specific conditions or input, `__new__` is your tool. This is particularly relevant for implementing design patterns like Singletons, where you need to ensure only one instance of a class exists. Subclassing Immutable Types: Built-in immutable types like `str`, `int`, `tuple`, etc., cannot have attributes assigned in their `__init__` method. If you want to subclass them and add specific behavior (e.g., a string that is always uppercase), you must use `__new__` to pass the appropriately modified data to the parent class's constructor during instance creation. Implementing Custom Memory Management or Allocation: In very rare and advanced cases, you might need to control how memory is allocated for an object. `__new__` is the hook for such low-level customizations. Metaclass Programming: When defining metaclasses, the `__new__` method of the metaclass is responsible for constructing the class object itself. This allows for deep customization of how classes are defined and behave.

In essence, if your goal is to alter the fundamental act of creating an object, or to create objects that deviate from the standard attribute-setting paradigm in `__init__`, then overriding `__new__` is necessary.

Is it possible for `__new__` to return an instance of a different class?

Yes, absolutely. This is one of the powerful capabilities of `__new__`. Since `__new__` is responsible for creating and returning the instance, it can, in fact, return an instance of a different class than the one it was called on. This is often used in factory patterns or when subclassing immutable types, as seen in the `UppercaseString` example where `super().__new__(cls, ...)` returns an instance that is ultimately a `str` (a base class).

However, there's a critical caveat: if `__new__` returns an instance of a class that is not a subclass of the original class (`cls`), then `__init__` will not be called on that returned instance. Python calls `__init__` only on instances that are created by `__new__` and are instances of `cls` or its subclasses. This behavior is a key aspect to remember when designing with `__new__`.

For example:

class OriginalClass: def __new__(cls, value): print(f"OriginalClass __new__ called for {cls.__name__}") if value % 2 == 0: # Return an instance of a different class (e.g., a wrapper or a more specific type) return EvenNumber(value) else: # Return an instance of the original class or a subclass return super().__new__(cls) def __init__(self, value): print(f"OriginalClass __init__ called for {self} with value {value}") self.value = value class EvenNumber(OriginalClass): # Inherits from OriginalClass def __init__(self, value): print(f"EvenNumber __init__ called for {self} with value {value}") super().__init__(value) # Call OriginalClass's __init__ self.is_even = True # Test cases print("Creating with odd value:") obj_odd = OriginalClass(5) print(f"obj_odd type: {type(obj_odd)}, value: {obj_odd.value}") print("-" * 20) print("Creating with even value:") obj_even = OriginalClass(10) print(f"obj_even type: {type(obj_even)}, value: {obj_even.value}") print(f"obj_even is_even: {obj_even.is_even}")

Output:

Creating with odd value: OriginalClass __new__ called for OriginalClass OriginalClass __init__ called for with value 5 obj_odd type: , value: 5 -------------------- Creating with even value: OriginalClass __new__ called for OriginalClass EvenNumber __init__ called for with value 10 obj_even type: , value: 10 obj_even is_even: True

As you can see, when `obj_odd` is created, `OriginalClass.__new__` returns an instance of `OriginalClass`, so `OriginalClass.__init__` is called. When `obj_even` is created, `OriginalClass.__new__` returns an instance of `EvenNumber`. Because `EvenNumber` is a subclass of `OriginalClass`, Python proceeds to call `EvenNumber.__init__`, which then calls `OriginalClass.__init__`.

What is the relationship between `__new__` and `__call__`?

This is a subtle but important distinction. `__new__` is invoked when you create an instance of a class. For example, `my_object = MyClass()`. This mechanism is part of the instance creation process.

`__call__` is a method that is invoked when you call an object *as if it were a function*. For example, if `my_object` is an instance of a class that has a `__call__` method defined, then `my_object()` will execute that `__call__` method.

The relationship becomes more apparent when dealing with metaclasses. A metaclass is a class that creates other classes. When you define a class using a metaclass (e.g., `class MyClass(metaclass=MyMeta): ...`), the metaclass's `__new__` method is called to create the `MyClass` object (the class itself). Then, when you instantiate `MyClass` (e.g., `instance = MyClass()`), Python internally calls the metaclass's `__call__` method. The `__call__` method of the metaclass is responsible for orchestrating the creation of instances of the class it created. By default, `type.__call__` (the default metaclass's call method) is what ultimately calls `__new__` and `__init__` on the class being instantiated.

So, while `__new__` is about creating instances of a class, `__call__` is about making an instance of a class callable like a function. Their connection is strongest in the context of metaclasses, where `__call__` on the metaclass triggers the instance creation process (which involves `__new__` and `__init__` on the target class).

Can `__new__` be used to make objects immutable?

Yes, `__new__` can be a crucial part of creating immutable objects, especially when dealing with subclasses of immutable built-ins. The key is to set the object's state (its attributes) within `__new__` itself, before `__init__` has a chance to run or before Python's default behavior for mutable types might allow for subsequent changes.

For example, when subclassing `str`, you cannot add attributes in `__init__`. You must use `__new__` to provide the desired string value to the `str` constructor. To enforce immutability further, you would typically combine this with other techniques like:

Using `__slots__`: This restricts the attributes an instance can have and prevents the creation of a `__dict__`. Overriding `__setattr__`: You can define a `__setattr__` method that raises an error if an attempt is made to change an existing attribute, effectively preventing modification after creation.

While `__new__` is essential for setting the initial state of an immutable object, it's often the combination of `__new__` and other mechanisms like `__slots__` and `__setattr__` that fully achieves immutability.

Conclusion

Understanding what `__new__` does in Python is a significant step toward mastering its object-oriented capabilities. It's the unsung hero of instance creation, working diligently behind the scenes, often overshadowed by the more frequently used `__init__`. While most Python developers will rarely need to override `__new__`, recognizing its role and understanding its behavior is vital for tackling advanced topics like metaclasses, Singletons, and the creation of immutable objects.

By carefully controlling the instance creation process, `__new__` offers a level of customization that `__init__` alone cannot provide. Whether you're ensuring a class has only one instance, building custom immutable data types, or delving into the meta-programming world of metaclasses, `__new__` is a powerful tool in your Python arsenal. Remember to always use `super().__new__(cls)` and to be mindful of whether `__init__` will be called based on what your `__new__` method returns. With this understanding, you can confidently navigate the depths of Python's object model.

What does __new__ do in Python

Copyright Notice: This article is contributed by internet users, and the views expressed are solely those of the author. This website only provides information storage space and does not own the copyright, nor does it assume any legal responsibility. If you find any content on this website that is suspected of plagiarism, infringement, or violation of laws and regulations, please send an email to [email protected] to report it. Once verified, this website will immediately delete it.。