Explain Codes LogoExplain Codes Logo

Why is init() always called after new()?

python
design-patterns
singleton
immutable-types
Alex KataevbyAlex Kataev·Aug 14, 2024
TLDR

__new__() in Python acts as the constructor, responsible for the creation of new instances. This operation must precede the initialization step performed within __init__(). Therefore, __new__ is always called before __init__.

class MyClass: def __new__(cls): print("Who ordered a new instance? Coming right up...") return super().__new__(cls) def __init__(self): print("Instance ready. Time to cook up some attributes.") obj = MyClass()

Output:

Who ordered a new instance? Coming right up...
Instance ready. Time to cook up some attributes.

Understanding new and init

In Python, __new__ and __init__ work together to control instance creation and initialization. Essentially, __new__ is the chef that prepares the uncooked instance, and __init__ is the gourmet artist that adds the final touches.

While you would typically not mess with __new__, there are use-cases that call for it. Especially when immutable types or design patterns like singletons or flyweights enter the scene.

Utilizing new and init in design patterns

Diving deeper into the practical uses and patterns where __new__ and __init__ play vital roles can help to make your Python programming more effective and maintainable.

The Factory pattern: An alternative to new

The Factory pattern is often a handy alternative to overloading the __new__ method, effectively managing object creation behind the scenes.

class CakeFactory: def bake_cake(self, cake_type): if cake_type == 'chocolate': return ChocolateCake() elif cake_type == 'vanilla': return VanillaCake() pastry_chef = CakeFactory() choco_cake = pastry_chef.bake_cake('chocolate') # Who doesn't love cake?

Singleton pattern: Being unique is tough

The Singleton pattern is a perfect showground for __new__, ensuring that only one instance exists. Here, a Metaclass or Class decorator can ensure uniqueness without touching __new__.

# Metaclass approach class Highlander(type): # There can be only one! _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] class Singleton(metaclass=Highlander): pass

Immutable types: Unchangeable yet customizable

When subclassing immutable types, overriding __new__ becomes essential, as __init__ cannot modify the instance:

class CustomString(str): def __new__(cls, content): return super().__new__(cls, content.lower()) # Whispering is the new shouting.

Patterns for resource conservation

Implementing patterns like Flyweight call for a resource-conservative approach. Check for the existence of an object before initiating a new instance:

class FlyweightMaker: _flyweights = {} @staticmethod def get_flyweight(key): if key not in FlyweightMaker._flyweights: FlyweightMaker._flyweights[key] = HeavyweightObject(key) return FlyweightMaker._flyweights[key] # Cause heavyweights don't like to fly alone!

The practicality of design patterns

Understanding the practical usage of design patterns like Factory, Singleton or Flyweight makes your Python skillset shiny. Meta-programming techniques like Metaclasses or Class Decorators often make these implementations cleaner and classier.

Immutable types and singletons

While dealing with immutable types, __new__ lets you control uniqueness on a per-instance basis. This control is handy when implementing patterns like Singleton, ensuring the existence of only a single instance.

The Python community never fails to innovate. Whether it's advanced patterns, helpful GEMs, or practical solutions, resources are abundant. Communities are continually exploring to simplify, optimize and revamp common solutions. Staying connected with these can make you a Python Jedi!