8ème et dernier chapitre sur la programmation orientée objet en Python. Nous allons voir l’ultime réalité, le secret cosmique de nirvana Pythonique, le truc dont personne ne se sert avant d’avoir au moins codé 3 moteurs de blogs, un bot twitter et une IA de real doll.
J’ai nommé…
Les métaclasses.
Comme tous les articles sur la question, je commence avec la citation standard :
Les métaclasses sont une magie trop obscure pour que 99% des utilisateurs s’en préoccupe. Si vous vous demandez si vous en avez besoin, ce n’est pas le cas (les gens qui en ont vraiment besoin le savent avec certitude et n’ont pas besoin d’explications sur la raison).
Tim Peters, les mec qui a écrit le PEP20.
En résumé, cet article ne vous servira probablement pas. Je me suis servi, en 10 ans, deux fois des métaclasses dans ma vie. C’est pour le sport, quoi.
Pré-requis:
- Avoir lu la partie précédente et bien tout compris. Mais alors bien tout. Si il subsiste un doute dans votre esprit, relisez toute la série car vous ne vous en sortirez pas.
- Notamment, avoir bien pigé le principe de
object
, car les metaclasses ne fonctionnent qu’avec les New Style classes. - Comprendre le principe de références, callable, lambdas et dictionnaires en Python. En fait à peu prêt tout sauf les métaclasses parce que ce truc est ce que vous voulez apprendre en dernier de toute façon.
- Être bien reposé et avoir du temps devant soi. Pas la peine de lire en diagonal. Vraiment.
Musique.
Au début, il y avait les objets
Et Dieu Guido vit que cela était bon, et qu’il n’y avait pas de raison de ne pas se marrer un bon coup, alors il décida que les classes, qui servaient à fabriquer les objets, seraient aussi des objets.
Dans la plupart des langages, une classe est vraiment juste un plan pour produire un objet, un bout de code, une syntaxe élaborée pour dire “voici ma classe”. On peut l’imaginer comme cela en Python également, et vivre très heureux et avoir beaucoup de bons moments :
>>> class CreateurDObject(object): ... pass ... >>> mon_objet = CreateurDObject() >>> print(mon_objet) <__main__.CreateurDObject object at 0x1c22fd0> |
Sauf qu’en Python, les classes sont aussi des objets.
Je vous laisse maturer ça 1 minute. Prenez une inspiration.
Quand l’interpréteur Python lit le mot class
, il crée un objet. Dans le code ci-dessus, en mémoire, Python crée un objet CreateurDObject
.
Cet objet est une classe, il permet de créer d’autres objets – ses instances – mais ça reste un objet. Et comme tous les objets…
On peut l’assigner à une variable :
>>> ReferenceACreateurDObjet = CreateurDObject >>> ReferenceACreateurDObjet() <__main__.CreateurDObject object at 0x1c320d0> |
On peut le passer en paramètre :
>>> def afficher_une_class(cls): ... print("Hey, on m'a passé la classe %s" % cls) ... >>> afficher_une_class(CreateurDObject) Hey, on m'a passé la classe <class '__main__.CreateurDObject'> |
On peut lui ajouter des attributs à la volée :
>>> CreateurDObject.nouvel_attribut = 'nouvelle valeur' >>> hasattr(CreateurDObject, 'nouvel_attribut') True >>> CreateurDObject.nouvel_attribut u'nouvelle valeur' |
Le 7eme jour, on se faisait grave chier alors on a créé des classes dynamiquement
Peut être vous souvenez-vous qu’on peut créer des fonctions à la volée ?
Parce qu’on peut faire pareil avec les classes :
>>> def fabriquer_une_class(nom): ... if nom == 'bulbizarre': ... class Bulbizarre(object): ... pass ... return Bulbizarre # je retourne Bulbizarre, PAS Bulbizarre() ... else: ... class Salameche(object): # qui prenait carapuce, sérieux ? ... pass ... return Salameche >>> Pokemon = fabriquer_une_class('autre') >>> type(Pokemon) # Pokemon est une CLASSE, pas une instance <type 'type'> >>> nouveau_pokemon = Pokemon() # la je crée une instance >>> type(nouveau_pokemon) <class '__main__.Salameche'> |
Une classe n’est pas quelque chose de figé dans le marbre, comme tout le reste en Python, on peut les fabriquer dynamiquement, les modifier en cours de route, les malmener, etc.
Aucun pokemon n’a cependant été blessé pendant la rédaction de cet article. Tiens je me demande si il y a des sites zoophiles spécialisés dans les Pokemons. Qu’est-ce que je raconte ? Évidement qu’il y en a. D’ailleurs Pikachu est comme une shocking flesh torch avec piles incluses quand on y pense. Ou un appareil à abdos. Il faut que je fasse des abdos, j’ai pris du bide. Heu… où j’en étais ?
Ah oui. Classes. Dynamiques.
Comme toutes les opérations courantes idées à la con, Python vous permet de faire ça complètement à la main. Maintenant vient la troisième Révélation Des Métaclasses : la fonction type()
a deux usages.
Dans une première vie type()
est une fonction ordinaire, elle sert à retourner le type d’un objet :
>>> print type(1) <type 'int'> >>> print type("1") <type 'str'> >>> print type(CreateurDObject) <type 'type'> >>> print type(CreateurDObject()) <class '__main__.CreateurDObject'> |
Et propose à sa logeuse de descendre ses poubelles. Mais elle a une autre vie électronique, elle est aussi capable de créer une classe.
Oui c’est très con d’avoir choisit la même PUTAIN DE FONCTION pour faire deux trucs qui n’ont rien à voir. Mais les deux usages ont un avenir et il va falloir faire avec, se caler ça où je pense et oublier votre avocat.
Bref, ça marche comme ça :
type(nom de la classe, tuple des parents de la classe, dictionnaire contenant les attributs)
Le nom de la classe, je pense que vous avez pigé.
Le tuple des parents, il peut être vide. C’est dans le cas où vous souhaitez un héritage : vous pouvez passer des références aux classes parentes.
Le dictionnaire est assez simple : chaque clé est un nom d’attribut, chaque valeur est une valeur d’attribut.
Exemple, cette classe :
>>> class Pokeball(object): ... pass |
Peut se créer ainsi :
>>> Pokeball = type('Pokeball', (), {}) |
Et s’utiliser tout pareil :
>>> print(Pokeball) <class '__main__.Pokeball'> >>> print(Pokeball()) <__main__.Pokeball object at 0x1c32450> |
Vous aurez noté que le nom de classe passé en paramètre est le même que celui de la variable qui va recevoir la classe ainsi créée. Oui, ils peuvent être différents. Non, ça ne sert à rien.
Et je sais que certains auraient préféré MyLittlePoney, mais je fais avec ma maigre culture G. Faudrait que je tente avec des noms de Pogs.
Un petit exemple avec des attributs :
>>> class Pokeball(object): ... couleurs = ('rouge', 'blanc') |
Ce qui donne :
>>> Pokeball = type('Pokeball', (), {'couleurs': ('rouge', 'blanc')}) >>> Pokeball.couleurs (u'rouge', u'blanc') |
Étant donné que les méthodes sont des attributs…
>>> class Pokeball(object): ... def attraper(self): ... print("ratééééééééééééé") ... |
Peut se traduire par :
>>> def out(): print "ratéééééééééééé" ... >>> Pokeball = type('Pokeball', (), {'attraper': out}) >>> Pokeball().attraper() ratéééééééééééé |
Au passage, toute classe créée par type
hérite automatiquement de object
, pas besoin de le préciser.
Petite démo avec de l’héritage :
>>> class Masterball(Pokeball): ... def attraper(self): ... print("yeahhhhhhhhhh") ... |
Se transforme en :
>>> def out(): print "yeahhhhhhhhhh" ... >>> Masterball = type('Masterball', (Pokeball,), {'attraper': out}) >>> Masterball().attraper() yeahhhhhhhhhh |
Je répète les Révélations Divines :
- Les classes sont des objets.
- On peut créer les classes à la volée.
type()
permet de créer une classe manuellement.- Keanu Reeves est un mauvais acteur.
Vous voyez où je veux en venir, là, non ?
Et le tout puissant déclara : tu créeras des classes avec des classes
Et tu n’aimeras pas ça. Alors au début tu le feras avec des fonctions parce que c’est plus facile.
Une métaclasse, c’est seulement le nom du “truc” qui fabrique une classe. C’est tout.
C’est pour ça qu’on appelle cela des métaclasses, les classes des classes.
Puisque les classes sont des objets ?
Vous suivez ?
Non ?
Ah.
o
En deux lignes alors :
UneClasse = MetaClass() un_objet = UneClasse() |
Mieux ?
Vous avez vu la fonction type()
créer des classes ? En fait la fonction type()
est une métaclasse. C’est la métaclasse dont Python se sert pour créer tous les objets classes, quand vous tapez le keyword class
.
Il est facile de voir ça en regardant l’attribut __class__
, qui indique quelle classe a servi à créer un objet :
>>> pokemon_capture = 149 >>> pokemon_capture.__class__ <type 'int'> >>> nom = 'magicarp' >>> nom.__class__ <type 'str'> >>> def attrapez_les_presque_tous(): pass >>> attrapez_les_presque_tous.__class__ <type 'function'> >>> class Pokedex(object): pass >>> gadget = Pokedex() >>> gadget.__class__ <class '__main__.Pokedex'> |
Mais quelle est le __class__
de tout __class__
?
>>> attrapez_les_presque_tous.__class__.__class__ <type 'type'> >>> pokemon_capture.__class__.__class__ <type 'type'> >>> nom.__class__.__class__ <type 'type'> >>> gadget.__class__.__class__ <type 'type'> |
Normalement c’est à ce moment là que vous avez la Grande Révélation.
Faites “Ahhhhhhhhhhhhhhhhh”.
Donc la métaclasse, c’est le machin qui fabrique les classes, c’est une factory de classe. type
est la métaclasse utilisée par défaut, mais vous pensez bien, chers amis, qu’on peut fabriquer ses propres métaclasses. Sinon ça serait pas marrant.
Ça marche comme ça :
class UneClasse(object): __metaclass__ = votre métaclasse [ le reste du code de la classe ] |
Quand Python va voir ça, il ne va pas créer la classe immédiatement, il va d’abord suivre la chaîne d’héritage pour trouver quelle métaclasse utiliser pour fabriquer la classe :
Il va d’abord vérifier si il y a __metaclass__
de déclarée. Si ce n’est pas le cas, il va chercher dans les parents si ils ont un attribut __metaclass__
. Si ce n’est pas le cas, il va utiliser type
. Si Python trouve __metaclass__
, alors il utilisera son contenu à la place de type
.
Dans les deux cas, il va collecter toutes les informations sur la classe (le nom, les parents, les attributs), et les passer en paramètres à type
ou votre métaclasse, et récupérer le résultat. Puis, seulement à ce moment là, il enregistre votre classe en mémoire.
Prenez votre temps sur ces paragraphes, c’est un peu la clé de tout le tuto.
Bon, nouvelle révélation : une métaclasse n’a pas besoin d’être une classe.
Je sais, c’est idiot d’appeler un truc “métaclasse” si ça n’a pas besoin d’être une classe. Mais on parle de la fonctionnalité qui utilise type
pour un truc qui n’a rien à voir. Ça devait être le jour du beaujolais nouveau quand ils ont introduit la feature.
En fait, une métaclasse peut être n’importe quel callable qui retourne une classe, donc une fonction toute conne fait très bien l’affaire.
Le but de la métaclasse, c’est d’intercepter la création de la classe afin de la modifier, et voici ce que ça donne :
# une métaclasse DOIT avoir la même signature que type() puisque # Python va lui passer tout ça automatiquement en paramètre def prefixer(nom, parents, attributs): """ On va créer une métaclasse qui prend tous les noms de méthodes, et les préfixes du mot "attaque". Oui ça ne sert à rien. Mais les usages des métaclasses qui servent à quelque chose sont généralement très compliqués. """ # On crée un nouveau dictionnaire d'attributs avec les noms préfixés # On fait gaffe à pas modifier les __méthodes_magique__. nouveaux_attributs = {} for nom, val in attributs.items(): if not nom.startswith('__'): nouveaux_attributs['attaque_' + nom] = val else: nouveaux_attributs[nom] = val # On délègue la création de la classe à type() : return type(nom, parents, nouveaux_attributs) |
L’utilisation est toute simple, on écrit une classe normale, et on lui rajoute __metaclass__
:
>>> class Ronflex(object): __metaclass__ = prefixer def armure(self): print("defense +15") def dodo(self): print("zzzzzz") >>> r = Ronflex() >>> r.attaque_armure() defense +15 >>> r.attaque_dodo() zzzzzz >>> r.dodo() Traceback (most recent call last): File "<ipython-input-58-90dd54234d5d>", line 1, in <module> r.dodo() AttributeError: 'armure' object has no attribute 'dodo' |
Voilà, c’est à peu près tout ce qu’il y a à comprendre des métaclasses :
- On intercepte la création classe.
- On modifie les paramètres.
- On retourne la classe customisée.
Maintenant, la grande question : à quoi ça sert ?
Généralement, ça sert à faire de jolies APIs. Par exemple, les ORM (peewee, SQLAlchemy, l’ORM de Django) utilisent les métaclasses pour avoir un style déclaratif :
class Article(Model): titre = model.CharField() |
Ici, Model
va contenir une métaclasse, comme c’est un parent, la métaclasse sera aussi appelée pour Article
, et elle peut donc agir pour modifier titre
à la volée.
En effet, quand vous faites :
>>> art = Article.get(id=13) >>> art.titre u'Python pour les méca-scriptophiles' |
Avec un ORM, vous récupérez la valeur de titre
dans la base de données et non un CharField()
. C’est le but : cacher des requêtes SQL et donner l’impression de manipuler des objets.
Ici le rôle de la métaclasse, c’est de prendre tous les attributs de types xxxField()
, et modifier la classe pour qu’accéder à l’attribut fasse la requête voulue à la base de données.
On peut aussi utiliser les métaclasses pour faire des vérifications : s’assurer que la classe n’utilise pas un nom que vous voulez réserver, ou qu’elle contient bien un attribut.
Il y a même des implémentations d’interfaces (ou plutôt de l’équivalent des classes abstraites en Java) pour Python utilisant les métaclasses. Je ne suis pas convaincu par leur utilité, mais c’est possible.
Soyez prophètes
C’est pas le tout, mais maintenant que vous avez compris, il est temps de limer les bords pour que vous alliez porter la bonne parole.
Donc déjà, bon à savoir : metaclass
est un paramètre de classe en Python 3. Ca donne ça:
class DePython3(metaclass=votre_metaclasse) |
Ensuite, il y des conventions de nommage. Tout comme on nomme self
le premier paramètre des méthodes, et *args
/ **kwargs
les paramètres dynamiques, les paramètres des métaclasses ont des noms conventionels. La métaclasse précédente s’écrirait donc plus proprement :
def prefixer(name, bases, dct): nouveaux_attributs = {} for nom, val in dct.items(): if not nom.startswith('__'): nouveaux_attributs['attaque_' + nom] = val else: nouveaux_attributs[nom] = val # On délègue la création de la classe à type() : return type(name, bases, nouveaux_attributs) |
Enfin, souvenez-vous que les métaclasses ne fonctionnent qu’avec les New Style classes, donc celles qui héritent de object
.
Par ailleurs, vous croiserez peut être des fois __metaclass__
en plein milieu d’un module (pas dans une classe donc). C’est une vieille façon de faire, qui affecte toutes les classes du module. Ce n’est plus recommandé.
Terminons sur une version de métaclasse qui fait la même chose que prefixer()
, mais sous forme de classe. Parce que sinon à quoi ça sert que ça s’appelle une métaclasse, hein ?
# Oui, on peut hériter de type(), car c'est une classe, enfin une métaclasse. # Mais pas sous forme de fonction. Sous forme de classe. Mais son nom # est en minuscule, comme celui des classes int() et str(). Qui ne sont # pas des métaclasses. Souvenez-vous : Beaujolais. class Prefixer(type): # __new__ est le vrai constructeur en Python, et il retourne l'instance # en cours d'une classe. Or, qu'elle est l'instance d'une métaclasse ? # Une classe ! def __new__(cls, name, bases, dct): nouveaux_attributs = {} for nom, val in dct.items(): if not nom.startswith('__'): nouveaux_attributs['attaque_' + nom] = val else: nouveaux_attributs[nom] = val # On délègue la création de la classe à son parent. # Notez que cls doit être passé de bout en bout, ce qui n'est pas # le cas d'habitude avec 'self' return super(Prefixer, cls).__new__(cls, name, bases, nouveaux_attributs) |
Et ça s’utilise pareil
>>> class Ronflex(object): __metaclass__ = Prefixer def armure(self): print("defense +15") def dodo(self): print("zzzzzz") ... >>> r = Ronflex() >>> r.attaque_dodo() zzzzzz >>> r.attaque_armure() defense +15 >>> r.dodo() Traceback (most recent call last): File "<ipython-input-66-90dd54234d5d>", line 1, in <module> r.dodo() AttributeError: 'Ronflex' object has no attribute 'dodo' |
Pourquoi utiliser une classe plutôt qu’une fonction alors que c’est vraiment plus compliqué ?
Et bien l’avantage d’utiliser une classe est qu’elle peut avoir des attributs et hériter. Car bien entendu, on peut avoir des metaclasses, qui héritent de metaclasses, et ainsi de suite, jusqu’à ce que du Lisp paraisse avoir du sens en comparaison.
On peut même faire plus vicieux et utiliser __call__
au lieu de __new__
:
# on hérite plus de type, rien à foutre class Prefixer(object): def __init__(self, prefix='attaque'): self.prefix = prefix # __call__ est la méthode appelée automatiquement quand on ajoute () après # un nom d'objet. Ca permet de rendre un objet callable. En gros, ça nous # permet d'utiliser une instance de Prefixer() comme une fonction def __call__(self, name, bases, dct): nouveaux_attributs = {} for nom, val in dct.items(): if not nom.startswith('__'): nouveaux_attributs[self.prefix + nom] = val else: nouveaux_attributs[nom] = val return type(name, bases, nouveaux_attributs) |
Et c’est sympa car du coup on peut passer des paramètres à notre métaclasse :
>>> class Ronflex(object): __metaclass__ = Prefixer(prefix='tatayoyo_') def armure(self): print("defense +15") def dodo(self): print("zzzzzz") ... >>> r = Ronflex() >>> r.tatayoyo_dodo() zzzzzz |
Voilà, vous avez vu le visage de Dieu en face. C’est comme ça qu’il fabrique le monde.
Ah mais attendez ? Si type()
est la métaclasse de tous les objets ? Qu’est-ce qui et la métaclasse de type()
?
>>> type.__class__ <type 'type'> |
Je vous laisse méditer sur cette découverte métaphysique.
Oh punaise. OK, je crois que j’ai compris. Mais je n’ai pas la moindre idée de où je pourrais éventuellement envisager d’utiliser ça en vrai. Je confirme que ça sert à rien !
Mais j’ai pas perdu mon temps, les petites remarques m’ont bien fait marrer. Je vais me mettre au Lisp.
J’ai bloqué sur ce paragraphe :
En fait python cherche l’attribut __metaclass__ en respectant l’héritage, tout simplement, mais je trouve que c’est tourné de manière bien complexe.
C’est vrai, j’ai mal tourné ma phrase.
Typo :
Tim Peters, LE mec qui a écrit le PEP20.
def fabriquer_une_class(nom):
… if name == ‘bulbizarre':
Alors, c’est nom ou name ? Faut se décider là…
Keanu Reeves est un mauvais acteur.
-> Tom hanks est un mauvais acteur.
Comment s’appelle alors le fait d’ajouter dynamiquement une propriété à une classe existante:
Soit la classe shapefile qui ne possède pas la propriété schema
import shapefile
reader = shapefile.Reader('strati.shp')
reader.schema
Traceback (most recent call last):
File "", line 1, in
AttributeError: Reader instance has no attribute 'schema'
mais si maintenant je veux rajouter la propriété/fonction schema à la classe shapefile:
def schema(reader):
.... traitement ....
return valeur
shapefile.Reader.schema = property(lambda self: schema(self))
reader.schema fonctionne
Ah merde, moi je l’ai utilisé qu’une seule fois en 10 ans.
J’ai raté ma viiiiiie…
ps (désolé) : Je pense qu’aujourd’hui, les décorateurs et les properties permettent de s’affranchir des métaclasses sur la plupart des anciens usages.
Mon cerveau dégouline un peu, mais j’y ai survécu.
Juste une remarque. Dans la première définition de la classe Ronflex, tu écris dans la console “r.att”. Et ensuite je suppose que tu fais Tab, pour avoir la liste de tous les attributs de r commençant par “att”.
C’est un peu confusionnant. Quand je vois un log de console python, je m’attends à ce que l’utilisateur a tapé sur Entrée à chaque ligne, et pas autre chose.
C’est d’autant plus confusionnant que “att” ressemble à “attributs”, et j’étais en train de me demander si c’était pas une fonction magique pour sortir tous les attributs d’une classe.
Faudrait, soit mettre un petit commentaire pour dire : “là, j’appuie sur Tab pour avoir l’auto-complétion”, soit carrément utiliser dir(r).
En tout cas, merci pour cet article, je suis content d’avoir appris un truc dont j’aurais pas besoin. C’est pas drôle de tout le temps apprendre des trucs utiles.
@martin: c’est du monkey patching.
Bon, j’ai rien compris ce qui en soit n’est pas très grave, mais je tenais à poster pour dire que la musique est vraiment cool :D.
Merci.
@Recher: c’est une faute de copie, je le retire.
@GM: +1. Effectivement, à part pour des features “héritées”, un peu d’instrospection, un usage de __new__ ou d’un décorateur sera plus facile, plus lisible et plus souple.
J’ai rien compris, mais… j’adore le petit trombone qui pop lorsqu’on… :D
Merci, j’aime bien ce genre de trucs. C’est tellement bien expliqué qu’on a l’impression de regarder par dessus l’épaule de dieu.
On en oublie même qu’on est sur tes épaules.
Le lien avec le flux RSS des commentaires ne s’affiche pas si aucun comm n’est affiche. Il faut taper manuellement l’adresse (http://sametmax.com/****/feed).
Sinon, il est classe ce tutoriel. (Ok, je sors)
Yop
C’est un mécanisme qu’on a utilisé dans mon ancien labo de recherche pour développer un langage basé sur Python, mais avec du contrôle de type en plus.
Le principe est qu’une personne écrit un modèle de données (pour un solveur numérique) basé sur ce langage. Le langage fournit toute une quincaillerie qui permet à l’auteur du modèle de préciser si des champs ont un type particulier (float), sont bornés (>0 pour une température en Kelvin), et d’autres joyeusetés.
Ensuite ce modèle peut être utilisé par un autre utilisateur comme base pour décrire un jeu de données pour le solveur. Le langage est accompagné d’un moteur qui utilise ce principe des métaclasses pour générer à la volée les classes qui vont bien.
Bref c’est pas connu, mais ça peut avoir son utilité.
My 2 cents…
Frodon
Class(e) comme d’habitude…et +1 avec Violette (sur le point que la musique est cool) !
Merci Sam & Max
;-)
Excellent comme d’habitude.
J’ai un peux décroché sur la fin mais la création du monde n’est pas encore dans mon emploi du temps donc j’aurai le temps d’y revenir.
Sinon:
– fix
s/que le classes/que les classes
– là chez moi ça veut pas (ça bloque sur “(” après print), idée?:
>> >> Pokeball = type(‘Pokeball’, (), {‘attraper': lambda self: print(“ratéééééééééééé”)})
File “”, line 1
Pokeball = type(‘Pokeball’, (), {‘attraper': lambda self: print(“ratéééééééééééé”)})
^
SyntaxError: invalid syntax
>> >>Masterball = type(‘Masterball’, (Pokeball,), {‘attraper': lambda self: print(“yeahhhhhhhhhh”)})
File “”, line 1
Masterball = type(‘Masterball’, (Pokeball,), {‘attraper': lambda self: print(“yeahhhhhhhhhh”)})
^
SyntaxError: invalid syntax
– alors la poule ou l’oeuf?
>>> type.__class__
Arf, c’est juste que j’avais
from __future__ import print_function
… Print ne marche pas dans une lambda en keyword. Je vais fixer ça.Ding ! les corrections.
# la je créer une instance
# là je crée une instance
shocking flech torch
shocking flesh torch
(Pas sûr)
A oui.
Ah oui.
oubliez votre avocat.
oublier votre avocat.
C’est pour ça qu’on appelle les métaclasses,
C’est pour ça qu’on appelle ça des métaclasses,
il d’abord suivre
il va d’abord suivre
le chaîne d’héritage
la chaîne d’héritage
qu’elle métaclasse
quelle métaclasse
Il va regarder d’abord vérifier
Il va d’abord vérifier
métaclass
métaclasse
qui n’a rien à avoir
qui n’a rien à voir
et les prefixe
et les préfixe
à quelques choses
à quelque chose
à peut prêt
à peu près
pas convaincus
pas convaincu
vous croisez
vous croiserez
Voili voilà, j’ai corrigé les articles qui me semblaient cool et importants. Pour le reste, je vous laisse vous débrouiller.
En tout cas, c’était fun, cette série sur la POO. Je connaissais, mais ça m’a permis de voir ou revoir certains détails.
Merci énormément pour tout se travaille. En relisant certaines fautes c’est carrément la honte :)
Le dernier de la série \o/
8ème : 8e
Si il subsiste : s’il
si il y a : s’il y a
Les classes sont des objets. : une espace avant Les
«Ahhhhhhhhhhhhhhhhh»
si il y a/si ils ont : s’il y a/s’ils ont
Que du pinaillage pour celui-ci. Et je fais partie des 99%.
Un link pour une liste d’usage des mataclasses http://python-3-patterns-idioms-test.readthedocs.org/en/latest/Metaprogramming.html