✋ Update: This post was originally published on my blog decodingweb.dev, where you can read the latest version for a 💯 user experience. ~reza
Python __new__()
method is static method (a.k.a magic or dunder method) that gives the programmer more control over how a specific class (cls) is instantiated.
This quick guide explains the __new__()
method and how and when to use it.
How does the Python __new__()
method work?
Python calls the __new__()
method every time you instantiate a class. It does the instantiation in two steps:
First: it invokes the __new__()
method of the class to create and return an instance (this instance is then passed to __init__()
as its first argument self
)
Next: the __init__()
method is invoked to initialize the object state. Please note the __init__()
method can’t return anything (except for None
).
When to use the Python __new__()
method?
Well, most of the time, you don’t need to!
Python does it all for you. However, you might want to override this dunder method if you need more control over the instantiation process.
Below are three [not-so-common] use cases of the __new__()
magic method:
- Subclassing built-in types
- Custom initialization logic
- Singleton classes
1. Subclassing built-in types: We create a subclass to create a new class based on a base class, which overrides some of its parent’s data/behavior.
In Python, you can subclass immutable built-in types (e.g., int
, float
, and str
) to add custom behavior to a built-in data type.
For instance, the bool
type is a subclass of the immutable int
. In fact, True
and False
are integer numbers 1
(for True) and 0
(for False).
Since the base class is int
, we can even do mathematical operations on boolean values.
>>> a = True
>>> b = False
>>> a + b
1
>>> a = True
>>> b = True
>>> a + b
2
>>> bool.__bases__
(,)
We can also create our own subclass of int (or any other data type) thanks to the __new__()
method.
Imagine you need a data type that stores integers as positive numbers regardless of the given value (1, 3, -45, -50). At the same time, you want to have access to all the functionalities the built-in int type offers:
class PositiveInt (int):
def __new__(cls, value):
return int.__new__(cls, abs(value))
In the above example, our subclass PositiveInt
extends the standard int
class, but it implements its own __new__()
method to transform the given value into a positive number.
Inside the __new__()
method, we call the __new__()
of the parent (int
) and pass it the absolute value (abs(value)
) of the given number.
Let's try it out:
b = PositiveInt(-34)
print(b)
# output: 34
c = PositiveInt(784)
print(c)
# output: 784
print(b + c)
# output: 818
Done!
2. Custom initialization logic: You can use the __new__()
method to create customized instantiation logic.
Imagine, you need to limit the number of instances created for a specific class:
class Players(object):
currentInstances = 0
maxInstances = 4
def __new__(cls, value):
if cls.currentInstances >= cls.maxInstances:
raise ValueError(f'You can only make {maxInstances} instances.')
cls.currentInstances += 1
return super().__new__(cls)
def __init__(self, value):
self.value = value
In the above class Player
, we have a variable named currentInstances
to store the current number of instances created.
In the __new__()
method, we ensure currentInstances
never exceeds maxInstances
. If it does so, we throw an exception. And if not, we instantiate the class by calling the parent object __new__()
method and return the result.
Finally, we increment currentInstances
by 1
.
If we try to instantiate the Player
class 5 times, we'll get an exception on the fifth try.
ValueError: You can only make 4 instances
3. Creating singleton classes: Another [controversial] use case for the __new__()
method is to create Python singleton objects.
Singleton classes should have only one instance.
Technically, you can use the __new__()
method to ensure only one instance of a specific class is created and returns the existing instance to all subsequent constructor calls.
Here's how we'd do it by using the __new__()
method:
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
In the above example, we define a class named Singleton with the attribute _instance
, initially set to None
.
When you instantiate the class, the __new__()
method is invoked, checking whether the _instance
attribute is None
.
If it is, it creates a new instance using the super().__new__(cls)
and assigns it to the _instance
attribute. If the _instance attribute already refers to an existing object, it returns it.
This ensures that only one instance of our class can exist at any given time.
While this works fine, it isn't the most Pythonic way of reusing objects. In fact, it's almost useless in Python!
A more Pythonic approach would be to use The Global Object Pattern. In Python, when you only need a single instance of a class, you probably don't need a class definition at all. You can place your data/functionality at a module level instead; Modules have their own namespaces and are singleton by design.
On the other hand, every time you refer to a module, Python returns the same module object. This is why we make singleton classes. Something that Python provides out of the box.
Sometimes it's acceptable, though! The one situation using the Singleton pattern sounds reasonable is when you're working with legacy code.
Let's say, due to new requirements, you need to use a specific class as a single object across the code base. However, it’s not an option to update all the legacy code to use the global object approach.
In that case, making the class singleton might be helpful to implement the requirement while keeping the old syntax.
I haven't personally encountered any of the above use cases, but you might!
Wrapping up
In conclusion, the __new__()
magic method provides lots of flexibility for controlling how a class is instantiated.
With the help of the __new__()
method, you can subclass built-on types, create singleton objects, and create classes with customized instantiation/ initialization logic.
I hope you found this quick guide helpful. Thanks for reading!
Top comments (0)