Qu'est-ce que __new__ en Python ?

Découvrez la méthode __new__ en Python : son rôle dans la création d'instances, sa différence avec __init__, et des exemples pratiques pour maîtriser ce concept avancé.
10 min de lecture
Believemy logo

Définition de __new__ en Python

La méthode __new__ est une méthode spéciale (ou dunder method) en Python qui intervient avant la méthode __init__ lors de la création d'un objet. Tandis que __init__ se charge d'initialiser une instance déjà créée, __new__ est responsable de la création même de cette instance en mémoire. C'est donc la toute première étape du cycle de vie d'un objet Python.

Concrètement, lorsque vous écrivez MonObjet(), Python appelle d'abord MonObjet.__new__(MonObjet) pour créer l'instance, puis MonObjet.__init__(instance) pour l'initialiser. La plupart du temps, vous n'avez pas besoin de redéfinir __new__, car le comportement par défaut hérité de la class object suffit. Cependant, cette méthode devient indispensable dans certains cas avancés que nous allons explorer en détail.

Si vous souhaitez approfondir ce sujet et maîtriser l'ensemble de la programmation orientée objet en Python, nous vous recommandons de suivre notre formation complète sur Python qui couvre ce type de concepts avancés.

 

Comprendre le rôle de __new__ dans la création d'objets

Pour bien comprendre __new__, il est essentiel de distinguer les deux phases de l'instanciation d'un objet en Python :

ÉtapeMéthodeRôleType de méthode
1. Création__new__Crée et retourne une nouvelle instanceMéthode statique implicite
2. Initialisation__init__Configure l'instance déjà crééeMéthode d'instance

La méthode __new__ reçoit la classe comme premier argument (conventionnellement nommé cls), contrairement à __init__ qui reçoit l'instance (nommée self). C'est logique : au moment où __new__ est appelée, l'instance n'existe pas encore.

Bon à savoir

__new__ est techniquement une méthode statique qui est traitée de manière spéciale par Python. Elle doit impérativement retourner une instance, sinon __init__ ne sera jamais appelée.

Signature de __new__

Voici la signature typique de __new__ :

PYTHON
class MaClasse:
    def __new__(cls, *args, **kwargs):
        instance = super().__new__(cls)
        return instance

    def __init__(self, *args, **kwargs):
        # Initialisation de l'instance
        pass

Vous remarquerez que __new__ appelle super().__new__(cls) pour déléguer la création réelle de l'objet à la classe parente (généralement object). C'est cette instruction qui alloue la mémoire et crée l'instance brute.

 

Le flux complet d'instanciation

Voici un exemple qui illustre l'ordre d'appel des méthodes :

PYTHON
class Demonstration:
    def __new__(cls, valeur):
        print(f"1. __new__ est appelé avec cls={cls.__name__}")
        instance = super().__new__(cls)
        print(f"2. Instance créée : {instance}")
        return instance

    def __init__(self, valeur):
        print(f"3. __init__ est appelé avec self={self}")
        self.valeur = valeur
        print(f"4. Initialisation terminée, valeur={self.valeur}")

obj = Demonstration(42)
# Affiche :
# 1. __new__ est appelé avec cls=Demonstration
# 2. Instance créée : <__main__.Demonstration object at 0x...>
# 3. __init__ est appelé avec self=<__main__.Demonstration object at 0x...>
# 4. Initialisation terminée, valeur=42

Ce flux montre clairement que __new__ précède toujours __init__ et que l'instance retournée par __new__ est exactement celle qui est passée à __init__ en tant que self.

 

Cas d'utilisation pratiques de __new__

Bien que la redéfinition de __new__ soit un cas avancé, il existe plusieurs situations où cette méthode s'avère indispensable. Voici les principaux cas d'utilisation.

1. Le patron Singleton

Le Singleton est un patron de conception (design pattern) qui garantit qu'une class ne peut avoir qu'une seule instance. C'est l'un des cas d'utilisation les plus classiques de __new__ :

PYTHON
class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, nom=None):
        if nom is not None:
            self.nom = nom

# Test du Singleton
a = Singleton("Premier")
b = Singleton("Second")

print(a is b)       # True : c'est le même objet
print(a.nom)        # "Second" (réinitialisé par le second appel)
print(id(a) == id(b))  # True
Attention

Attention : même si __new__ retourne la même instance, __init__ est appelée à chaque fois. Cela peut provoquer des réinitialisations inattendues. Pour éviter cela, vous pouvez ajouter un drapeau pour n'initialiser qu'une seule fois.

Voici une version améliorée qui évite la réinitialisation :

PYTHON
class SingletonSafe:
    _instance = None
    _initialized = False

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, nom=None):
        if not SingletonSafe._initialized:
            self.nom = nom
            SingletonSafe._initialized = True

a = SingletonSafe("Premier")
b = SingletonSafe("Second")

print(a.nom)  # "Premier" (pas réinitialisé)
print(b.nom)  # "Premier"

 

2. Sous-classer des types immuables

Les types immuables comme int, str, tuple ou frozenset ne peuvent pas être modifiés après leur création. Puisque __init__ intervient après la création, il est trop tard pour changer la valeur d'un objet immuable. Vous devez donc utiliser __new__ :

PYTHON
class PositiveInt(int):
    """Un entier qui est toujours positif (valeur absolue)."""

    def __new__(cls, valeur):
        # On transforme la valeur AVANT la création de l'objet
        return super().__new__(cls, abs(valeur))

print(PositiveInt(-42))   # 42
print(PositiveInt(15))    # 15
print(type(PositiveInt(-7)))  # 

Voici un autre exemple avec les chaînes de caractères :

PYTHON
class UpperStr(str):
    """Une chaîne de caractères toujours en majuscules."""

    def __new__(cls, contenu):
        return super().__new__(cls, contenu.upper())

texte = UpperStr("bonjour le monde")
print(texte)         # "BONJOUR LE MONDE"
print(type(texte))   # 
Bon à savoir

Ce mécanisme est impossible avec __init__ seul, car les objets immuables sont déjà "figés" au moment où __init__ est appelée. C'est pourquoi __new__ est indispensable ici.

 

3. Système de registre et factory pattern

Vous pouvez utiliser __new__ pour implémenter un système de registre qui retourne automatiquement la bonne sous-classe en fonction des arguments :

PYTHON
class Animal:
    _registry = {}

    def __init_subclass__(cls, type_animal=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if type_animal:
            Animal._registry[type_animal] = cls

    def __new__(cls, type_animal, nom):
        if cls is Animal:
            sous_classe = cls._registry.get(type_animal, cls)
            return super().__new__(sous_classe)
        return super().__new__(cls)

    def __init__(self, type_animal, nom):
        self.nom = nom

    def parler(self):
        return "..."


class Chat(Animal, type_animal="chat"):
    def parler(self):
        return f"{self.nom} fait : Miaou !"


class Chien(Animal, type_animal="chien"):
    def parler(self):
        return f"{self.nom} fait : Ouaf !"


# Utilisation
animal1 = Animal("chat", "Moustache")
animal2 = Animal("chien", "Rex")

print(type(animal1))       # 
print(animal1.parler())    # Moustache fait : Miaou !
print(type(animal2))       # 
print(animal2.parler())    # Rex fait : Ouaf !

Ce patron est très puissant pour créer des architectures extensibles où de nouvelles sous-classes peuvent être ajoutées sans modifier le code existant.

 

4. Cache d'instances (Object Pool)

Vous pouvez utiliser __new__ pour réutiliser des instances existantes au lieu d'en créer de nouvelles, ce qui est utile pour optimiser la mémoire :

PYTHON
class Couleur:
    _cache = {}

    def __new__(cls, nom):
        nom_normalise = nom.lower().strip()
        if nom_normalise in cls._cache:
            return cls._cache[nom_normalise]
        instance = super().__new__(cls)
        cls._cache[nom_normalise] = instance
        return instance

    def __init__(self, nom):
        self.nom = nom.lower().strip()


rouge1 = Couleur("Rouge")
rouge2 = Couleur("rouge")
rouge3 = Couleur("  ROUGE  ")

print(rouge1 is rouge2)  # True
print(rouge1 is rouge3)  # True
print(len(Couleur._cache))  # 1

 

__new__ et les métaclasses

La méthode __new__ joue également un rôle crucial dans les métaclasses. Une métaclasse est une class dont les instances sont elles-mêmes des classes. La méthode __new__ d'une métaclasse contrôle donc la création des classes, pas des instances :

PYTHON
class MetaValidation(type):
    """Métaclasse qui vérifie que chaque classe a une docstring."""

    def __new__(mcs, name, bases, namespace):
        if not namespace.get('__doc__'):
            raise TypeError(
                f"La classe '{name}' doit avoir une docstring."
            )
        return super().__new__(mcs, name, bases, namespace)


class MonService(metaclass=MetaValidation):
    """Service principal de l'application."""

    def executer(self):
        pass

# Ceci lèverait TypeError :
# class ServiceSansDoc(metaclass=MetaValidation):
#     pass

Dans ce contexte, __new__ reçoit quatre arguments : la métaclasse (mcs), le nom de la classe à créer, ses bases (classes parentes) et son espace de noms (dict contenant ses attributs et méthodes). C'est un outil puissant pour valider ou transformer des classes lors de leur définition.

 

Différences entre __new__ et __init__

Récapitulons les différences fondamentales entre ces deux méthodes pour éviter toute confusion :

Caractéristique__new____init__
Premier argumentcls (la classe)self (l'instance)
RôleCréer l'instanceInitialiser l'instance
Valeur de retourDoit retourner l'instanceNe retourne rien (None)
Ordre d'appelAppelée en premierAppelée en second
Type de méthodeMéthode statique impliciteMéthode d'instance
Fréquence de redéfinitionRarementTrès souvent
Utilisation principaleTypes immuables, singletons, métaclassesConfiguration de l'objet

 

Bonnes pratiques avec __new__

Voici les recommandations essentielles pour utiliser __new__ correctement dans vos projets Python :

  • Ne redéfinissez __new__ que si nécessaire : dans la grande majorité des cas, __init__ suffit. Utiliser __new__ sans raison ajoute de la complexité inutile.
  • Appelez toujours super().__new__(cls) : oublier cet appel empêchera la création de l'instance et provoquera des erreurs difficiles à diagnostiquer.
  • Retournez toujours une instance : si __new__ ne retourne pas une instance de la classe, __init__ ne sera pas appelée.
  • Transmettez les arguments correctement : assurez-vous que *args et **kwargs sont correctement propagés à super().__new__() quand c'est nécessaire.
  • Documentez votre utilisation : lorsque vous redéfinissez __new__, ajoutez une docstring expliquant pourquoi cette redéfinition est nécessaire.
  • Pensez à la compatibilité avec l'héritage : si votre classe sera sous-classée, assurez-vous que votre implémentation de __new__ fonctionne correctement avec les classes filles.
Attention

Évitez de mettre de la logique d'initialisation dans __new__. Cette méthode est destinée à la création de l'instance, pas à sa configuration. Gardez la séparation des responsabilités entre __new__ (création) et __init__ (initialisation).

Voici un exemple de ce qu'il ne faut pas faire :

PYTHON
# ❌ Mauvaise pratique : logique d'initialisation dans __new__
class Mauvais:
    def __new__(cls, nom, age):
        instance = super().__new__(cls)
        instance.nom = nom    # ❌ Ceci devrait être dans __init__
        instance.age = age    # ❌ Ceci aussi
        return instance


# ✅ Bonne pratique : séparation claire des responsabilités
class Bon:
    def __init__(self, nom, age):
        self.nom = nom
        self.age = age

 

Cas particulier : __new__ qui retourne un objet d'un autre type

Un comportement intéressant et parfois déroutant : si __new__ retourne un objet qui n'est pas une instance de la classe, alors __init__ ne sera pas appelée :

PYTHON
class Surprise:
    def __new__(cls):
        print("__new__ appelé")
        return "Je suis une chaîne, pas une instance de Surprise"

    def __init__(self):
        print("__init__ appelé")

obj = Surprise()
print(obj)        # "Je suis une chaîne, pas une instance de Surprise"
print(type(obj))  # 
# Note : __init__ n'est JAMAIS appelé ici

Ce comportement est utilisé dans certains cas avancés, mais il peut être source de confusion. Gardez-le à l'esprit lorsque vous déboguez des problèmes liés à l'instanciation.

 

Questions fréquentes

Question

Quelle est la différence entre __new__ et __init__ en Python ?

__new__ est responsable de la création de l'instance en mémoire et reçoit la classe (cls) comme premier argument. __init__ est responsable de l'initialisation de l'instance déjà créée et reçoit l'instance (self) comme premier argument. __new__ est appelée avant __init__ et doit retourner une instance, tandis que __init__ ne retourne rien. Dans la pratique quotidienne, vous n'aurez besoin de redéfinir que __init__ dans la grande majorité des cas.

 

Question

Quand faut-il utiliser __new__ plutôt que __init__ ?

Vous devez utiliser __new__ dans trois cas principaux : lorsque vous sous-classez un type immuable (comme int, str, tuple) et que vous devez modifier la valeur avant la création de l'objet ; lorsque vous implémentez le patron Singleton pour garantir une instance unique ; et lorsque vous travaillez avec des métaclasses pour contrôler la création des classes elles-mêmes. En dehors de ces cas, préférez toujours __init__.

 

Question

Pourquoi __init__ n'est pas appelé après __new__ dans certains cas ?

Si __new__ retourne un objet qui n'est pas une instance de la classe (ou d'une de ses sous-classes), Python n'appelle pas __init__. C'est un mécanisme de sécurité : __init__ attend un objet du bon type en self. Si vous oubliez de retourner une instance (par exemple en oubliant le return), __new__ retournera implicitement None, et __init__ ne sera pas appelée non plus.

 

Question

Comment apprendre à maîtriser __new__ et la POO avancée en Python ?

La maîtrise de __new__ et des concepts avancés de la programmation orientée objet en Python nécessite de la pratique et un apprentissage structuré. Nous vous recommandons de suivre notre formation dédiée à Python sur Believemy, qui couvre en profondeur les méthodes spéciales, les métaclasses, les patrons de conception et bien d'autres sujets essentiels pour devenir un développeur Python compétent.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.