PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Le guide ultime et définitif sur la programmation orientée objet en Python à l’usage des débutants qui sont rassurés par les textes détaillés qui prennent le temps de tout expliquer. Partie 1.

mardi 15 janvier 2013 à 08:30

Prérequis à ce tuto bien chargé :

Intro

Il y a des tas de manières de programmer. Des styles. Des formes que l’on donne au code. On leur donne des noms: programmation procédurale, fonctionnelle, orientée flux, par contrat, etc. C’est ce qu’on appelle des paradigmes, des points de vue sur comment on doit faire le boulot.

En vérité, le point de vue n’est pas déterminant. Vous pouvez faire le même boulot en utilisant n’importe lequel. L’important c’est de coder.

Mais chaque point de vue possède des caractéristiques et des outils différents.

Ce que vous allez voir est ce qu’on appelle la programmation orientée objet, ou POO. C’est un simple point de vue, un outil, mais il est très utilisé en Python, Ruby ou Java.

Quand vous avez appris la programmation, on vous a montré comment stocker des données dans des structures de données:

Et on vous a montré comment créer un comportement pour votre programme en utilisant des mots clés, puis plus tard en utilisant des fonctions pour regrouper ces mots clés.

En Python, à ce stade, vous utilisez des fonctions pour agir sur des structures de données.

La programmation orienté objet, c’est un style de programmation qui permet de regrouper au même endroit le comportement (les fonctions) et les données (les structures) qui sont faites pour aller ensemble.

C’est tout. C’est une simple question d’organisation du programme.

Qu’est-ce qu’un objet

Un objet est un… truc. Un machin. Un bidule.

Ça peut vous paraître une définition floue, mais c’est parce que c’est exactement ce que peut être un objet: n’importe quoi que vous décidiez de coder. L’objet est un moyen de dire à la machine, ce < entrez_ici_un_nom_de_truc > possède telle donnée, et fait telle chose avec.

En Python, absolument tout est un objet : une chaîne, un entier, un dictionnaire, une liste, une fonction… Vous avez donc manipulé des objets sans le savoir. Maintenant vous allez créer les votre.

Créer des objets se fait en deux étapes: décrire à quoi ressemble votre objet, et demander à l’ordinateur d’utiliser cette description pour le fabriquer.

En Python 2.x, cela donne ceci:

class DescriptionDeLObject:
    pass
>>> object_tout_neuf = DescriptionDeLObject()
>>> print object_tout_neuf
<__main__.DescriptionDeLObject instance at 0x3112440>

La description de l’objet (le plan de construction) est :

class DescriptionDeLObject:
    pass

C’est ce qu’on appelle une classe. La classe est le moyen, en Python, de décrire à quoi va ressembler un objet.

On dit ici:

“Cet objet s’appelle DescriptionDeLObject.”

C’est tout.

Ensuite, on crée l’objet:

>>> object_tout_neuf = DescriptionDeLObject()

DescriptionDeLObject() (notez les parenthèses), est la syntaxe Python pour dire “fabrique un objet à partir de ce plan”. Le nouvel objet va être retourné, et mis dans la variable object_tout_neuf.

La variable object_tout_neuf contient un objet issue de la classe DescriptionDeLObject, on dit qu’il contient une instance de DescriptionDeLObject. C’est pour cela que l’action de créer un objet à partir d’une classe est appelée instanciation.

Python ne sait pas grand chose sur cet objet si ce n’est son nom et son adresse en mémoire. Si on fait print dessus, c’est donc ce qu’il vous donne:

>>> print object_tout_neuf
<__main__.DescriptionDeLObject instance at 0x3112440>

On peut créer autant d’objets que l’on veut à partir d’une classe :

>>> DescriptionDeLObject()
<__main__.DescriptionDeLObject instance at 0x31c1d40>
>>> DescriptionDeLObject()
<__main__.DescriptionDeLObject instance at 0x31d6cf8>
>>> DescriptionDeLObject()
<__main__.DescriptionDeLObject instance at 0x31d6bd8>
>>> DescriptionDeLObject()
<__main__.DescriptionDeLObject instance at 0x31d6c68>

Méthodes

Les méthodes sont des fonctions déclarées à l’intérieur de la classe. Methode est juste un nom pour dire “cette fonction est dans une classe”.

class DescriptionDeLObject:
 
    def la_methode(objet_en_cours):
 
        print 'Edgar Morin'

Nous avons créer une “fonction” nommée une_methode dans le bloc de la classe. Puisqu’elle est dans ce bloc, cette fonction est ce qu’on appelle une méthode.

Elle n’existe pas en dehors de la classe.

>>> la_methode()
Traceback (most recent call last):
  File "<ipython-input-12-c02691e8aa46>", line 1, in <module>
    la_methode()
NameError: name 'la_methode' is not defined

Mais, elle est attachée à chaque objet. On peut l’utiliser en faisant < variable_contenant_objet >.< nom_de_méthode >. Ainsi, pour appeler la méthode une méthode, il nous faut forcément une instance :

>>> objet_tout_neuf = DescriptionDeLObject()
>>> objet_tout_neuf.la_methode()
Edgar Morin

Arrivé à ce stade, vous devez vous demander “quel est ce objet_en_cours” qui est défini comme paramètre de la méthode ?

C’est une spécificité de Python : quand vous appelez une méthode depuis un objet, l’objet est automatiquement passé en premier paramètre par Python. C’est automatique, et invisible.

C’est très facile à comprendre en faisant une méthode qui retourne objet_en_cours pour voir ce qu’il y a dedans :

>>> class DescriptionDeLObject:
...         
...         def une_methode(objet_en_cours):
...         
...                 return objet_en_cours
...     
>>> ze_object = DescriptionDeLObject()
>>> print ze_object 
<__main__.DescriptionDeLObject instance at 0x3429170>
>>> print ze_object.une_methode()
<__main__.DescriptionDeLObject instance at 0x3429170>

Python fait comme si on avait fait :

ze_object.une_methode(ze_object)

objet_en_cours et ze_object contiennent la même chose : l’objet en cours. objet_en_cours est juste le nom que nous avons donné à notre premier argument, mais Python passe toujours automatiquement en premier argument l’objet en cours afin que vous puissiez modifier cet objet à l’intérieur d’une méthode.

On dit donc que objet_en_cours est l’instance courante.

Une méthode doit donc toujours déclarer au moins un paramètre pour accueillir la référence à l’objet en cours, sinon ça plante. Voyez plutôt :

class DescriptionDeLObject:
 
    def methode_sans_objet_en_cours():
 
        print "test"
 
    def methode_avec_objet_en_cours(objet_en_cours):
 
        print "test"
 
>>> ze_object = DescriptionDeLObject()
>>> ze_object.methode_avec_objet_en_cours() 
test
>>> ze_object.methode_sans_objet_en_cours()
Traceback (most recent call last):
  File "<ipython-input-19-90434b7e7bfa>", line 1, in <module>
    ze_object.methode_sans_objet_en_cours()
TypeError: methode_sans_objet_en_cours() takes no arguments (1 given)

Python dit que methode_sans_objet_en_cours est déclarée sans argument mais on lui en a passé un tout de même. Et en effet, Python lui a passé de manière invisible l’objet en cours en tant que premier argument.

En résumé, on déclare une classe avec une méthode ainsi:

class DescriptionDeLObject:
 
    def nom_de_methode(objet_en_cours):
 
        faire quelque chose ici

Et on l’utilise ainsi:

variable = DescriptionDeLObject()
variable.nom_de_methode()

Conventions

En Python, peu de choses sont forcées. La plupart de choses sont des conventions. Mais ce sont des conventions fortes, les gens y tiennent.

Parmi ces conventions, il y a les conventions de nommage, à savoir:

La convention la plus surprenant est celle du premier paramètre des méthodes qui contient l’objet en cours. Son nom n’est pas forcé, contrairement aux autres langages (comme this en Java par exemple), en fait c’est un paramètre tout à faire ordinaire. Néanmoins, la (très très forte) convention est de l’appeler self :

class DescriptionDeLObject:
 
    def nom_de_methode(self):
 
        faire quelque chose ici

Appelez-donc TOUJOURS le premier paramètre de chaque méthode self, et rappelez-vous qu’il contiendra toujours l’instance courante.

Enfin, les concepteurs de Python ont ajouté une convention supplémentaire : les méthodes appelées automatiquement sont appelées __nom_de_methode__ (avec deux underscores de chaque côté).

En effet, il existe un certains nombre de méthodes que vous pouvez écrire, et si vous les nommez d’une certaine façon, elle seront appelées automatiquement quand une condition spéciale (liée au nom), se présente.

Nous verrons cela avec la méthode __init__.

Attributs

Vous avez vu que les méthodes étaient juste des fonctions attachées à un objet. Et bien les attributs sont juste des variables attachés à un objet.

En fait, techniquement, les méthodes sont aussi des attributs, mais faisons comme si ce n’était pas le cas et séparons :

Comme Python est un langage dynamique, on peut ajouter n’importe quel attribut à n’importe quel objet en utilisant la syntaxe < objet >.< nom_attribut > :

>>> print ze_object.un_attribut # l'attribut n'existe pas
Traceback (most recent call last):
  File "<ipython-input-30-b52a599929f9>", line 1, in <module>
    ze_object.un_attribut
AttributeError: DescriptionDeLObject instance has no attribute 'un_attribut'
 
>>> ze_object.un_attribut = "valeur de l'attribut"
>>> print ze_object.un_attribut
valeur de l'attribut

Et c’est là que vous allez voir l’interêt de self :

class DescriptionDeLObject:
 
    def afficher(self):
 
        # self est l'objet en cours, donc on à accès à ses attributs !
        print self.un_attribut
 
    def modifier(self):
 
        # on peut modifier les attributs depuis l'intérieur
        self.un_attribut = "autre valeur"

On peut accéder aux attributs depuis l’intérieur et l’extérieur d’un objet :

>>> ze_object = DescriptionDeLObject() 
>>> ze_object.afficher() # l'attribut n'existe pas encore
Traceback (most recent call last):
  File "<ipython-input-35-c10abd45fc98>", line 1, in <module>
    ze_object.afficher() # l'attribut n'exite pas
  File "<ipython-input-33-3e1eb4d8e5f6>", line 5, in afficher
    print self.un_attribut
AttributeError: DescriptionDeLObject instance has no attribute 'un_attribut'
 
>>> ze_object.un_attribut = "maintenant il existe !"
>>> ze_object.afficher() 
maintenant il existe !
>>> ze_object.modifier() # l'attribut est modifié par la méthode
>>> ze_object.un_attribut 
'autre valeur'

Comme une méthode est juste une fonction attachée à un objet, on peut lui passer des paramètres. Et une méthode a accès à l’objet en cours, elle peut modifier l’objet en cours.

Donc, une méthode peut modifier l’objet en cours en fonction des paramètres passés :

class DescriptionDeLObject:
 
    def afficher(self):
 
        print "La valeur actuelle est %s" % self.un_attribut
 
    def modifier(self, valeur):
 
        self.un_attribut = valeur * 2
 
>>> ze_object = DescriptionDeLObject()
>>> ze_object.un_attribut = 1 # on met un entier en valeur cette fois
>>> ze_object.afficher()
La valeur actuelle est 1
>>> ze_object.modifier(2) # on passe un paramètre 
>>> ze_object.afficher()
La valeur actuelle est 4

Les méthodes servent exactement à cela : retourner les valeurs à l’intérieur de l’objet, ou à les modifier.

Initialisation d’un objet

On souhaite généralement donner une état de départ à tout nouvel objet créé. Par exemple, si vous travaillez sur un jeu vidéo de course de voiture, vous voudrez peut-être créer un objet voiture avec du carburant et une couleur de peinture.

Il serait contre productif de devoir les spécifier à chaque fois. Pour automatiser le travail, Python met à disposition des méthodes appelées automatiquement quand une condition est remplie. Ce sont les méthodes nommées __methode__.

Dans notre cas, on veut que notre objet ait un état de départ, donc on va utiliser la méthode qui est appelée automatiquement après la création de l’objet. C’est la méthode __init__.

Si vous appelez une méthode avec le nom __init__, Python va automatiquement, et de manière invisible, appeler la méthode après avoir créé l’objet :

class DescriptionDeLObject:
 
    def __init__(self):
 
        print "L'objet a été créé !"
        print "La méthode est appelée automatiquement"
 
>>> ze_object = DescriptionDeLObject()
L'objet a été créé !
La méthode est appelée automatiquement

__init__ est une méthode tout à fait ordinaire. Seul le nom est important: comme elle est appelée ainsi, Python l’a détecte automatiquement et l’appelle au bon moment.

C’est très utile pour fournir un état de départ:

class Voiture:
 
    def __init__(self):
 
        self.carburant = 100
        self.couleur = "noire"
 
>>> v = Voiture()
>>> v.couleur
'noire'
>>> v.carburant
100

Bien entendu, on veut généralement pourvoir changer l’état de départ. Ici encore, Python nous aide: si vous passez des paramètres à l’instanciation, ils sont passés automatiquement à __init__.

Comme __init__ est une méthode ordinaire (et donc une fonction), on peut lui donner des paramètres avec des valeurs par défaut mais accepter une nouvelle valeur si elle est passée :

class Voiture:
 
    def __init__(self, combien_de_carburant=100, quelle_couleur="noire"):
 
        self.carburant = combien_de_carburant
        self.couleur = quelle_couleur
 
 
>>> v1 = Voiture() # sans paramètres, les valeurs par défaut sont utilisées
>>> v1.couleur
'noire'
>>> v1.carburant
100
>>> v2 = Voiture(50, 'rouge et jaune à petit pois') # on change les valeurs
>>> v2.couleur 
'rouge et jaune \xc3\xa0 petit pois'
>>> v1.couleur
'noire'
>>> v3 = Voiture(quelle_couleur="fushia virant sur l'aigue marine")
>>> v3.couleur
"fushia virant sur l'aigue marine"
>>> v3.carburant
100

Les valeurs passées dans Voiture(valeurs, passées), sont automatiquement repassées à __init__(self, valeurs, passées).

En résumé : __init__ est appelé automatiquement après que l’objet soit créé, et on lui passe les paramètres passées à l’instanciation. Comme __init__ est une méthode et a accès à l’instance en courante, elle peut ajouter des attributs à l’objet en cours en fonction de ces paramètres. On utilise ça pour créer des objets avec un état de départ par défaut, et permettre une personnalisation de cet état de départ.

J’ai mis des noms différents pour les paramètres d’__init__ et les attributs de l’objet, mais en réalité on leur met souvent le même nom par convention. Donc l’exemple ci-dessus s’écrirait plutôt :

class Voiture:
 
    def __init__(self, carburant=100, couleur="noire"):
 
        self.carburant = carburant
        self.couleur = couleur

carburant et couleur ne sont pas la même chose que self.carburant et self.couleur : les premiers n’existent que dans __init__. Une fois qu’__init__ a fini de s’exécuter, ils disparaissent. Les seconds sont des attributs de l’objet en cours, et sont donc toujours disponibles tant que l’objet existe. Le fait qu’ils portent le même nom ne pose aucun problème car la syntaxe self. les différencies.

Résumé final

La classe, c’est un plan.

L’objet, c’est ce qu’on créé avec le plan.

Une méthode, c’est une fonction déclarée dans une classe (qui est attachée à chaque objet produit, et on lui passe en premier paramètre l’objet en cours).

Un attribut, c’est une variable attachée à un objet.

Une instance d’une classe, c’est l’objet issu d’une classe.

Un spermophile, c’est un animal dont la queue est assez courte et plus ou moins fournie. Il aime être à 4 pattes et mange parfois des glands. Ça n’a rien à voir, mais je me suis dit qu’il fallait faire passer l’info. Ah oui, et la cuniculture, c’est prendre soin d’un lapinou. La levrette, par contre, n’est pas la femelle du lièvre.

Le vocabulaire en informatique, c’est primordial.

(La partie 2 arrivera quand elle arrivera)

Testez vos Webapp sur iPhone / iPad avec le Simulator iOS – [Mac]

lundi 14 janvier 2013 à 08:19

La plupart d’entre vous le savent certainement mais moi je viens de le découvrir :)

Au début j’utilisais Ripple mais ce n’est pas de l’émulation, juste de l’encapsulage donc pas top.

Hier un pote me parle d’iOs Simulator , un émulateur iPhone / iPad livré avec Xcode. J’ai été surpris par la qualité de l’outil, un petit executable de 5 Mo qui émule très bien l’iPhone, l’iphone Retina ainsi que l’iPad.

Pour se le procurer c’est un peu galère, il faut télécharger Xcode (4GB!) dans le dev center, avoir un compte dev donc. ça se passe ici . C’est pas la mort mais y a plus simple…

Pour le trouver une fois installé..CHERCHEZ ! De tous les sites que j’ai parcouru aucun n’avait le même emplacement que moi, les dev de chez apple adorent faire des blagues. ça va donc dépendre de votre version de Xcode, de votre OS, du temps qu’il fait.
Un indice quand même c’est dans un des sous répertoires /Developer/ .
Moi il est dans /Developer/Platforms/iPhoneSimulator.platform/Developer/Applications et il se nomme “Simulateur iOS”

Pratique pour tester ses webapps sur iPhone / iPad

L’utilisation est nickel et j’ai pu corriger quelques bugs que je n’avais pas sur Ripple sous chrome ou même sous Safari. Ceci dit d’après un autre pote qui lui bosse en tant que dev sur les machines apple, rien ne vaut un appareil original, même avec l’émulateur il peut y avoir des problèmes.

Android a lui aussi un émulateur dans son SDK téléchargeable ici

Pour le lancer c’est un peu plus compliqué long car il faut configurer un appareil.

Une fois le SDK téléchargé, allez dans le répertoire adt-bundle-mac-x86_64/sdk/tools et lancez la commande ./android avd qui va avoir pour but de lancer le manager d’appareils, depuis ce manager vous allez pouvoir créer un nouvel appareil si il n’y en a pas déjà et le lancer (bouton “start”).

Configurer votre terminal pour qu’il vous notifie de la fin d’une commande longue

dimanche 13 janvier 2013 à 07:44

UndistractMe: un petit software que je n’ai testé que sous Ubuntu (donc je ne sais pas si ça marche ailleurs, ni comment l’installer) qui vous envoit une notification quand une commande qui met plus de 10 secondes à tourner prend fin.

Capture d'écran de undistract-me

Dumb ways to die, so many dum ways to die !

Très pratique quand on lance une installe ou des tests unitaires.

Pour l’installer:

$ sudo add-apt-repository ppa:undistract-me-packagers/daily
$ sudo apt-get update
$ sudo apt-get install undistract-me

Et ensuite rajouter dans son .bashrc:

notify_when_long_running_commands_finish_install

Pour que ça fonctionne il faut aussi activer “Executer les commandes comme shell de connexion” dans les options de votre terminal. Par exemple pour Guake:

Capture d'écran des options de Guake

C'est à peu prêt la même chose pour Gnome Terminal

Modifiez la variable LONG_RUNNING_COMMAND_TIMEOUT dans /usr/share/undistract-me/long-running.bash si 10 secondes vous parait trop long ou trop court.

Le module operator en Python

samedi 12 janvier 2013 à 11:17

Le module operator contient des fonctions qui font ce que font les opérateurs. Exemple :

>>> from operator import add, gt
>>> add(1, 1) # fait en fonction ce que ferait 1 + 1
>>> gt(1, 2) # fait en fonction ce que ferait 1 > 2

On a donc dans le module operator de quoi remplacer les opérateurs +, <, not, etc. par des fonctions.

Les opérateurs or et and n'ont pas de fonctions équivalentes car ils short circuitent, ce que ne peut pas faire une fonction. operator.and_ et operator.or_ sont des fonctions pour & et |, par pour or et and.

Il y a des opérateurs qu'on oublie souvent, comme l'opérateur []. Le module operator contient des fonctions pour lui également.

>>> from operator import getitem, getslice
>>> lst = [1, 2, 3]
>>> lst[0]
1
>>> getitem(lst, 0)
1
>>> lst[:2]
[1, 2]
>>> getslice(lst, 0, 2)
[1, 2]

On oublie parfois l'opérateur ".". Il y a une fonction builtin pour ça:

>>> class Classe(object):
...     def __init__(self, type):
...         self.type = type
...         
>>> c = Classe('cm2')
>>> c.type
'cm2'
>>> getattr(c, 'type')
'cm2'

Mais le plus étonnant dans le module operator, c'est qu'il a des fonctions qui retournent... des fonctions.

>>> from operator import itemgetter
>>> getfirst = itemgetter(0)
>>> getfirst([1, 2, 3])
1
>>> getfirst('abc')
'a'
>>> getsecond = itemgetter(1)
>>> getsecond([1, 2, 3])
2
>>> getsecond("abc")
'b'
>>> getlast = itemgetter(-1)
>>> getlast(range(100))
99

Ici on créer des fonctions qui ont pour seul triste but dans la vie de retourner l'élément à l'index donné. getfirst retournera l'élément 0. getsecond retournera l'élément à l'index 1. getlast retournera l'élément à l'index -1. itemgetter() est un créateur de fonction qui ne font que ça: retourner un seul élément.

On pourrait aussi le faire avec une lambda:

>>> getfirst = lambda x: x[0]
>>> getfirst([1, 2, 3])
1

On a le même chose de chose pour les attributs. attrgetter est une fonction qui crée une fonction. La fonction créée retourne toujour la valeur du même attribut.

>>> from operator import attrgetter
>>> gettype = attrgetter('type') # crée une fonction qui ...
>>> gettype(Classe('cm2')) # ... retourne la valeur de l'attribut 'type'
'cm2'
>>> gettype(Classe('6eme'))
'6eme'

On pourrait aussi le faire avec une lambda:

>>> gettype = lambda x: x.type
>>> gettype(Classe('6eme'))
'6eme'

Ces fonctions n'ont pas l'air utile comme ça, mais en Python les fonctions peuvent être passées en paramètres. Cela permet donc de faire des choses intéressante comme de l'injection de dépendance. C'est notamment ainsi que fonctionne key dans sorted().

Bref, jetez un coup d'oeil à operator, il est plein comme un oeuf:

>>> from operator import <tab>
abs               delitem           getslice          ilshift           ior               isSequenceType    le                neg               sequenceIncludes
add               delslice          gt                imod              ipow              is_               lshift            not_              setitem
and_              div               iadd              imul              irepeat           is_not            lt                or_               setslice
attrgetter        eq                iand              index             irshift           isub              methodcaller      pos               sub
concat            floordiv          iconcat           indexOf           isCallable        itemgetter        mod               pow               truediv
contains          ge                idiv              inv               isMappingType     itruediv          mul               repeat            truth
countOf           getitem           ifloordiv         invert            isNumberType      ixor              ne                rshift            xor

La différence entre __new__ et __init__ en Python

vendredi 11 janvier 2013 à 11:06

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:

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, cls).__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.