En Python, la création d'un objet de classe est un processus en deux étapes :
__new__ est appelée, responsable de l'allocation et du retour d'une nouvelle instance de la classe.__init__ est appelée, qui initialise l'objet déjà créé.__new__ — création (peut retourner n'importe quel nouvel objet ; paramètre obligatoire — classe).__init__ — initialisation (travaille avec self, l'instance déjà allouée).__new__ est redéfini pour les héritiers de types immuables (tuple, str, int), pour créer des singletons et le patron "fabrique".class MyStr(str): def __new__(cls, value): print("__new__ appelé") instance = super().__new__(cls, value.upper()) # modification de la valeur avant la création return instance def __init__(self, value): print("__init__ appelé", value) s = MyStr('abc') # __new__ appelé -> __init__ appelé abc print(s) # 'ABC'
Peut-on initialiser un objet immuable via __init__, si __new__ n'est pas implémenté dans la classe ?
Non ! Par exemple, pour les types immuables (comme str, int, tuple), toute modification des champs/valeurs doit se faire dans __new__, sinon, il est impossible de modifier la valeur dans __init__.
class MyTuple(tuple): def __init__(self, items): print(f'__init__ ! {items}') def __new__(cls, items): print(f'__new__ ! {items}') return super().__new__(cls, map(str, items)) t = MyTuple([1, 2, 3]) print(t) # ('1', '2', '3')
Histoire
Dans un grand projet Django, on a implémenté l'héritage de str pour stocker des types de chaînes particuliers. On a essayé de modifier la valeur dans __init__ — mais le résultat ne changeait pas, car str est immuable. Cela a été corrigé seulement après avoir étudié et redéfini __new__.
Histoire
Lors de l'implémentation du patron Singleton, on a oublié de mettre en place la logique de retour de l'instance existante dans __new__ : plusieurs appels à la classe créaient toujours de nouveaux objets, ce qui entraînait une fragmentation de la mémoire.
Histoire
Dans une bibliothèque de sérialisation des données, lors de la mise en cache, on a utilisé une classe avec __init__ redéfini, mais on a négligé que pour la mise en cache, il fallait retourner le même objet via __new__. En conséquence, le cache était cassé à chaque nouvel appel, car différents objets étaient créés.