Les méthodes __new__
et __init__
n’ont rien de spécial. Ce sont des méthodes ordinaires. Mais parce qu’elles sont nommées ainsi, Python les détecte et les appelle automatiquement a un moment précis.
Ce moment, c’est ce qui différencie __init__
de __new__
.
__init__
pour initialiser
__init__
est la méthode qui va être appelée automatiquement après qu’un objet ai été crée. Ce n’est pas un contructeur du tout, c’est un initialiseur.
Si vous faîtes ça:
>>> class Premiere(object): ... ... def __init__(self, prix): ... print "%s euros" % prix ... >>> c = Premiere(10000) 10000 euros |
A la ligne c = Premiere(10000)
, Python va créer une instance de la classe Première()
. Il va ensuite immédiatement et automatiquement appeler __init__
en lui passant cette instance en premier argument et les paramètres passés par l’appel Premiere(paramètres)
. Donc, quand __init__
est appelé, l’objet instancié existe déjà.
On va utiliser __init__
pour initialiser l’objet, c’est à dire pour lui donner son état de départ: changer les attributs, configurer l’objet par rapports aux arguments, etc.
Dans tous les autres langages, on utiliserait le constructeur pour faire ce boulot. Pas en Python.
L’avantage de __init__
, c’est qu’il est très facile à manipuler. Il n’y a pas de magie dangereuse dans __init__
: on a l’objet tout neuf, et les arguments passés à l’instancitation, on peut donc manipuler l’objet sans se soucier du reste. Ici on attache deux attributs à l’instance self
:
>>> class Premiere(object): ... discount = False ... def __init__(self, prix): ... self.prix = prix ... if self.prix < 5000: ... self.discount = True ... >>> c = Premiere(10000) >>> c.discount False |
Comme en Python les attributs sont dynamiques, on peut attacher un argument même si l’instance ne le déclare pas, et il est créé automatiquement.
En résumé: __init__
est appelé automatiquement APRES la création de l’objet, et on met dedans le code d’initialisation de l’objet (généralement une modification des attributs pour leur donner leur état de départ).
__new__
pour créer
__new__
est le vrai constructeur. Pour cette raison, elle doit retourner un objet.
>>> class Premiere(object): ... ... def __new__(cls, prix): ... print "%s euros" % prix ... return super(Premiere, cls).__new__(cls) ... >>> c = Premiere(10000) 10000 euros |
__new__
est appelée AVANT la création de l’objet, car c’est son boulot de créer l’instance et de la retourner. Comme on ne sait pas retourner une instance nous même (enfin si, mais pas dans cet article :-)), on appelle super()
pour utiliser la méthode __new__
de object
et créer une instance pour cette classe.
L’objet créé sera ensuite passé à __init__
automatiquement par Python.
On utilise rarement __new__
. Les deux cas principaux sont:
- si on hérite d’un type immutable (str, int, tuple, etc),
__new__
est le seul endroit où on puisse initialiser l’objet. - dans le cas des métaclasses.
En résumé: __new__
est le vrai constructeur, il est appelé pour créer l’objet, et l’objet ainsi instancié est passé à __init__
. Vous n’avez presque aucune raison de vous en servir, c’est vraiment pour les cas particuliers.
Voici l’ordre d’éxécution:
>>> class Premiere(object): ... def __new__(cls, prix): ... print "__new__" ... return super(Premiere, cls).__new__(cls) ... def __init__(self, *args): ... print "__init__" >>> c = Premiere(10000) __new__ __init__ |
Exemple d’utilisation de __new__
Généralement on sait très bien utiliser __init__
, mais __new__
est moins évident.
L’usage le plus fréquent de __new__
quand on hérite d’objets immutables. Par exemple, si vous voulez faire un objet Temperature
qui hérite de float
et qui accepte une unité en plus, ceci ne va pas marcher:
class Temperature(float): def __init__(self, value, unit): super(Temperature, self).__init__(value) self.unit = unit def __str__(self): return "%s%s" % (self.value, self.unit) print Temperature(10, '°C') Traceback (most recent call last): File "<ipython-input-1-65b676255e09>", line 11, in <module> Temperature(10, '°C') TypeError: float() takes at most 1 argument (2 given) |
La raison est que du fait de la nature immutable de float
, il est initialisé dans __new__
, et il n’attend aucune valeur de plus dans __new__
, mais on lui passe malgré tout (via Temperature(10, '°C')
).
En revanche, ceci va marcher:
class Temperature(float): def __new__(cls, value, unit): instance = super(Temperature, cls).__new__(cls, value) instance.unit = unit return instance def __str__(self): return "%s%s" % (super(Temperature, self).__str__(), self.unit) print Temperature(10, '°C') 10.0°C |
Comme on override __new__
, on lui donne la possibilité d’accepter une argument de plus.
Un autre exemple serait de vouloir créer une chaîne de caractères qui est toujours en majuscule (ce qui est bien moins utile que l’exemple précédent):
class CapsLockString(str): def __init__(self, value): print value # et maintenant je fais quoi ? print CapsLockString('test') test test |
Ça ne plantera pas, mais il n’y a rien que nous puissions faire car str
est immutable. On ne peut tout simplement pas faire quoique ce soit avec value
. Avec __new__
, on peut faire quelque chose sur la chaîne intermédiaire:
class CapsLockString(str): def __new__(cls, value): return super(CapsLockString, cls).__new__(cls, value.upper()) print CapsLockString('test') TEST |
Deux chaînes sont en fait créées, une normale, puis une en majuscule retournée par upper() qui va servir de valeur à notre objet (en fait il y en a même 3 dans l’implémentation CPython, c’est pour ça que les notations littérales sont plus rapides que l’usage des classes pour créer des built-in).
__new__
permet donc essentiellement de créer de jolis API. On l’utilise par ailleurs dans les metaclasses, mais ce sera pour un autre article.
Un troisième usage de __new__
, assez rare (mais en même temps utiliser __new__
est déjà rare), c’est le pattern factory. Les javaistes le connaissent bien, c’est un motif de conception qui permet de gérer la création d’objets qui peuvent eux même créer des objets, qui créer des objets qui… Bref.
Car en fait __new__
peut retourner n’importe quoi. Il peut retourner toujours la même instance pour faire un singleton par exemple. On peut même carrément renvoyer un truc qui n’a rien n’a voir, par exemple une fonction :
class FonctionFactory(object): def __new__(self, value, repeat): def repeater(string=value): return string * repeat return repeater >>> function = FonctionFactory('hello', 2) # création de la fonction >>> print function() hellohello >>> print function('bonjour') bonjourbonjour |
Ici on retourne carrément une fonction, et pas du tout une instance de FonctionFactory()
comme prévu. On pourrait faire ceci de manière plus simple avec de la programmation fonctionnelle, mais __new__
permet de bénéficier de tout l’outillage de la POO.
D’accoooord! C’est super ça!
Des use cases pour le __new__ des type builtins aurait fait de cette article un article incontournable. Tanpis intéressant malgré tout.
Tu as raison, je pourrais rajouter ça. Par contre, juste le demander sans chichi aurait fait de ce commentaire un commentaire plus agréable à lire. Tanpis intéressant malgré tout.
je savais pas comment tourner mon commentaire désolé pour les chichi
J’ai mis quelques exemples en plus du coup. Dis moi si ça mérite des ajouts.
Super, j’ai appris plein de truc, mes idées sont plus clairs. J’avais déjà vu un exemple de ChaineOrdonnable qui héritais de str et ça faisait que la comparaison était faites sur la deuxieme valeur passé en argument, par exemple ça donnait:
Chez moi quand je fait super(str, cls).__new__(cls, value) il me dit
Merci à toi.
Un autre exemple, c’est numpy : un numpy.array utilise __new__, donc quand on veut créer une classe enfant, c’est plus délicat. Surtout que ça utilise encore une autre fonction derrière.Bref, c’est le bazar : http://docs.scipy.org/doc/numpy/user/basics.subclassing.html
Très bon article! Merci.
Si quelqu’un a des exemples de __new__ utilisé en production ce serait pas mal.
Donc d’après ce que je comprends, on utilise __new__quand on veut utiliser des méthodes d’une classe immutable c’est ça ?
Un petit article sur les immutables pourrait peut être intéresser d’autres personnes aussi :)
(Ah et hors sujet total: http://www.linfo.re/Un-python-voyage-sur-l-aile-d-un-avion
Héhé ça embouche un coin aux fans de PHP ça hein! C’est pas un éléphant qui ferait ça!)
@Kontre: j’avais vu ça. Je pense qu’ils font ça pour des raisons de perf.
@Amirouche: si j’ai pas le code, je peux pas debug :-)
Pourquoi faire compliqué quand on peux faire simple. Très bon post.
super les exemples
s/immutable/immuable/g
ça sonne quand même beaucoup mieux, et ça n’entache plus la qualité de l’article.
@Krypted: peewee a un exemple d’utilisation de __new__ en prod. Il faut le lire lentement par contre :-)
@jpcw: Oui mais dans les docs on lit immutable partout, donc je garde volontairement le terme pour qu’un débutant puisse faire facilement le lien. Il faut faire des concessions.
On pourra aussi noter que new est la seule classmethod implicite de Python (il n’est pas nécessaire de précéder la méthode new du décorateur @classmethod)
Dans cette classe:
class Premiere(object):
... def new(cls, prix):
... print "%s euros" % prix
... return super(Premiere, cls).new(cls)
je m’étonne qu’il faille passer cls en argument de new
Quand on utilise super() pour aller chercher le init d’une classe parente, on utilise la syntaxe:
super(MyClass, self).init()
il n’y a pas le self passé en argument du init(). il est passé automatiquement.
Alors pourquoi cls est passé en argument de new ?
La réponse est assez surprenante:
__new__
est n’est pas une méthode de Foo mais de object, qui est attachée à Foo. C’est aussi une méthode statique. Toute classe custo en Python hérite de object, mais comme new est appelée très souvent, plutot que de faire un look up au parent à chaque fois ce qui est couteux, Python utilise cette astuce pour avoir la meme methode partout.Une petite coquille dans la classe Temperature:
def init(self, value, unit):
…super(Temperature, cls).init(value)
remplacer cls par self
ça ne change pas le message d’erreur car l’erreur se produit avant, à l’appel de new
Ah new est une méthode statique. Dans ce cas Gilles Lenfant s’est trompé dans son commentaire, il dit que c’est une classmethod
C’est le cls qui est trompeur.
Merci pour les typos.
Mais du coup en héritant float on peut aditionner des objets température avec un + ?
Du style une pseudo surcharge d’opérateur comme en C++
Et si on peut, peut on aussi renvoyer une exception dans le cas où l’unité ne serait pas la même ?
Yep:
Mais bon autant utiliser une lib existante type https://pypi.python.org/pypi/units/