je suis débutant en python et actuellement je suis entrain de suivre la formation:Apprenez à programmer en python qui , d'ailleurs est une trés bonne formation mais j'ai un petit souci avec les décorateurs; voici le probléme:
pour créer une classe singleton, le code comme vous devez le savoir est le suivant ( j'ai copié le code donné dans la formation) :
def singleton(classe_definie):
instances = {} # Dictionnaire de nos instances singletons
def get_instance():
if classe_definie not in instances:
# On crée notre premier objet de classe_definie
instances[classe_definie] = classe_definie()
print(instances) # j'ai rajouté cette ligne pour voir le contenu du dictionnaire
return instances[classe_definie]
return get_instance
ensuite on fait appel au décorateur:
@singleton
class Test:
pass
a=Test()
jusque la y'a pas de probléme et on arrive a obtenir une seule instance d'une classe, mnt si je définis une autre classe Test2 par exemple qui fait appel au décorateur, quand je crée une instance de test2, notre dictionnaire ne contient plus que l'instance de Test2, ce qui est normal puisque une fois le décorateur exécuté toutes les variables sont détruites, le probléme c'est que si j'essaie de créer une nouvelle instance de Test et bien ca renvoie la premiere instance qu'on a créé cad a , ma question est comment le décorateur sait que une instance Test a deja eté créé?
Le sujet est déplacé de la section Discussions générales vers la section Langage Python
Merci d'utiliser la mise en forme de code
Les forums d'Openclassrooms disposent d'une fonctionnalité permettant de colorer et mettre en forme les codes source afin de les rendre plus lisibles et faciles à manipuler par les intervenants. Pour cela, il faut utiliser le bouton de l'éditeur, choisir un des langages proposés et coller votre code dans la zone prévue. Si vous utilisez l'éditeur de messages en mode Markdown, il faut utiliser les balises <pre class="brush: c;">Votre code ici</pre>.
En effet cet exemple est encore une fois pas terrible, le dictionnaire ne contiendra jamais plus d'un élément.
Ce qui fait que le décorateur fonctionne, c'est le mécanisme de portée des variables en Python.
Tu dis que toutes les variables sont détruites une fois le code exécuté mais ce n'est qu'en partie vrai. Les variables existent tant qu'elles sont référencées.
Ici, une fois l'application du décorateur, une classe Test devient égale à singleton(Test), c'est à dire égale à la fonction get_instance créée dans singleton.
Le mécanisme de fermeture fait que les variables définies dans singleton soient alors accessibles dans get_instance, ce qui est le cas de instances.
Donc en fait, la portée d'instances est liée à celle de get_instance, renvoyée par le décorateur. Et comme c'est Test qui est égal à get_instance, instances existe tant que Test existe, donc aucun soucis.
L'autre question que tu pourrais te poser, c'est pourquoi instances est différente pour chaque classe. Et là encore c'est la portée et le principe de fermeture qui l'expliquent : un dictionnaire instances est créé à chaque application du décorateur, et c'est ce nouveau dictionnaire et uniquement lui qui sera lié à la fonction get_instance.
Mais on rentre là dans des détails avancés du langage, qui peuvent être assez difficiles à comprendre.
Merci pour votre réponse, ne craignez pas de rentrer dans des détails compliqués, je comprend trés vite et ça peut m'éclairer sur des notions que je ne connais pas encore, d'ailleurs je ne connaissais pas la notions des closures et j'étais faire un tour du coté de wikipédia et la ... je n'ai toujours rien compris ( comme d'habitude avec wikipedia, je ne comprend jamais leurs articles), aprés j'ai fais une recherche sur le net et j'ai trouvé un trés bon article qui explique la notion des closures ce qui m'a permis de mieux comprendre pourquoi python se rappelle de la valeur du dictionnaire.
pour la 2éme question ( pourquoi instances est différent pour chaque classe) je me suis posé aussi cette question mais je voulais poser une question à la fois, en effet le dictionnaire ne contient qu'une seule valeur à la fois, et c'est celle de la dernière instance, si par exemple je crée une instance d'une classe a , instances contient la classe a , ensuite si je crée une instance d'une classe b, instances ne contient plus que la classe b, le problème c'est si après je crée encore une instance de la classe a et bien python se rappelle toujours qu'une 1 ére instance de a a eté crée et donc la nouvelle instance aura la meme référence que la 1ére. comment est ce possible.
newgateport a écrit:
> en effet le dictionnaire ne contient qu'une seule valeur à la fois, et c'est celle de la dernière instance, si par exemple je crée une instance d'une classe a , instances contient la classe a , ensuite si je crée une instance d'une classe b, instances ne contient plus que la classe b, le problème c'est si après je crée encore une instance de la classe a et bien python se rappelle toujours qu'une 1 ére instance de a a eté crée et donc la nouvelle instance aura la meme référence que la 1ére. comment est ce possible.
C'est bien ce que j'essayais d'expliquer, et c'est pourquoi je dis que la compréhension de ce mécanisme n'est pas évidente.
Non, instances n'est pas égal au dictionnaire de la dernière instance : il ya plusieurs dictionnaires instances (un par classe) et tous coexistent.
Mais les décorateurs (et le mauvais exemple de ce cours) sont probablement une difficulté supllémentaire à la comprhénesion des fermetures.
Repartons sur un cas plus simple, celui d'une fonction d'addition.
def add_with(a):
def addition(x):
return a + x
return addition
Nous avons ici une fonction add_with qui prend un paramètre numérique et retourne une fonction d'addition.
Nous pouvons créer quelques fonctions ainsi : add_3 = add_with(3), add_5 = add_with(5).
Chacune sera une sous-fonction addition différente, dans l'environnement de variables (le binding) sera lui aussi différent.
Dans add_3, le contexte/environnement/fermeture fera que a = 3, alors que dans add_5 on aura a = 5.
Il n'est pas question d'antériorité de l'une par rapport à l'autre, les deux coexistent, et d'ailleurs a n'existe pas en dehors de ces fonctions d'addition, les valeurs de a n'existent que par et pour ces fonctions.
Merci Entwanne, votre dernier exemple m'a eclaircis bien des choses, en gros si j'ai bien compris python crée un espace mémoire ou il va piéger la valeur de instances pour chaque classe qui appelle le décorateur, et ces espaces sont indépendants les uns des autres. mnt je me pose une autre question, imaginons qu'il y'ait une autre variable en plus de instances et qu'on veuille que toutes les classes qui font appel au décorateur partagent cette variable, est ce que le mot clé global résoudrait le problème ou est ce que c'est pas possible à l'interieur du déocrateur?
j'ai vu aussi que vous avez rédigé vous même une formation sur python que je trouve trés intéressante (surtout que vous abordez des notions avancés de python), j'irai voir une fois que je termine la formation actuelle.
et si c'est pas trop demander quel serait votre suggestion pour un décorateur singleton?
Une variable globale permettra en effet ce comportement, mais le mot-clé global n'y est pas nécessaire. Simplement déplacer la définition instances = {} en dehors de la fonction singleton et ne rien changer au reste fonctionnera correctement.
Ma suggestion serait de ne pas faire de décorateur singleton, qui est rarement un bon pattern. Si je devais le refaire, je reprendrai une fonction du même genre mais définissant instance = None plutôt que instances = {}.
Une variable globale permettra en effet ce comportement, mais le mot-clé global n'y est pas nécessaire. Simplement déplacer la définition instances = {} en dehors de la fonction singleton et ne rien changer au reste fonctionnera correctement.
C'est normal, et encore une fois cela découle des règles de scoping de Python.
Quand Python lit une définition de variable (toto = ...), il la prend automatiquement comme une déclaration, considérant alors que la variable est déclarée dans le scope courant, c'est ce qui se passe ligne 8 dans ton code : une variable instance est créée dans le scope de la fonction get_instance, prenant le pas sur celle de la fonction singleton.
Et une autre règle de scoping veut qu'une variable ne puisse pas être utilisée en lecture avant d'avoir été définie (ça paraît logique, vu qu'elle n'a encore aucune valeur cela poserait un problème). C'est aussi ce qui se passe ici : tu essaies d'accéder ligne 6 à une variable qui n'a pas encore été définie dans ce scope.
Là on peut se poser deux questions :
Pourquoi cela fonctionnait avec instances dans le premier exemple ? Parce que instances n'était jamais redéfinie/redéclarée dans la fonction get_instance, donc tous les accès à la variable depuis le scope de get_instance faisaient référence à la variable de singleton.
Comment faire pour modifier une variable d'un scope extérieur ? Car c'est ce que l'on veut, redéfinir une variable appartenant à un scope parent. Pour cela il faut dissocier la déclaration de variable de sa définition. Tout en haut de la fonction get_instance, on va définir la variable instance avec l'instruction nonlocal instance. Cela indique à Python qu'une variable instance est déclarée dans un scope parent, et qu'on veut la redéfinir dans le courant.
Le mot-clé global fonctionne de manière semblable pour permettre de redéfinir les variables déclarées dans le scope global du module (mais souvent ce mot-clé qui fait intervenir des aspects compliqués du langage n'est jamais correctement expliqué).
thetui a écrit:
> Ce n'est pas ce que tu as fait.
En effet, il a plutôt appliqué la solution que je conseillais.
d'abord ça fonctionne à merveille avec le mot clé nonlocal.
Comme a dit entwanne , je voulais appliquer sa solution qui m'a semblé plus propre et direct, et je voulais éviter d'utiliser le mot clé global car je croyais que instance était accessible depuis get_instance étant donné qu'elle appartenait au scope parent( mnt je sais qu'elle est accessible uniquement en lecture et pas en écriture) et que si j'utilise global elle serait accessible de partout ce que je veux pas.
2émement cela fait pas mal de notions que j'ai appris ces derniers 24 h.
enfin, un grand merci Entwanne, c'est grâce aux gens comme toi que des débutants comme moi arrivent à progresser.
Et ça me fait penser qu'il faudrait écrire quelque chose là-dessus, car ce sont des problèmes qui reviennent fréquemment auxquels il n'y a pas de réponse évidente à apporter.
et beh écoute c'est que du plaisir pour moi personnellement, les notions comme closures, décorateurs et génerateurs méritent une attention particuliére (en meme temps je ne suis qu'au début), par exemple la formation que je suis actuellement entrain de suivre éxplique bien ce que c'est que les générateurs , mais je ne me suis rendu compte de l'utilité des générateurs qu'en allant sur des sites anglais ou la j'ai vu toute leur puissance ( pour faire du multithreading par exemple).
entwanne a écrit:
> Et ça me fait penser qu'il faudrait écrire quelque chose là-dessus, car ce sont des problèmes qui reviennent fréquemment auxquels il n'y a pas de réponse évidente à apporter.
entwanne a écrit: > Et ça me fait penser qu'il faudrait écrire quelque chose là-dessus, car ce sont des problèmes qui reviennent fréquemment auxquels il n'y a pas de réponse évidente à apporter.
Un grand merci @entwanne pour le temps passé à rediger ce cours https://zestedesavoir.com/tutoriels/3163/variables-scopes-et-closures-en-python/ super clair et qui m'apporte enfin une possibilité de comprendre les mécanismes à l'oeuvre, et entre autre où est stocké le dictionnaire "instances" (ou "cache" dans ton exemple) et comment y accéder (à des fins pédagogiques de compréhension).
Cela faisait des heures que je cherchais une explication claire, et tu as raison de souligner dans ton post que ce sujet n'est pas couramment abordé sur le net de manière satisfaisante. Merci de l'avoir fait!
- Edité par P_A Heurtebize 19 novembre 2019 à 13:07:50
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
entwanne — @entwanne — Un zeste de Python — La POO en Python — Notions de Python avancées — Les secrets d'un code pythonique
entwanne — @entwanne — Un zeste de Python — La POO en Python — Notions de Python avancées — Les secrets d'un code pythonique
entwanne — @entwanne — Un zeste de Python — La POO en Python — Notions de Python avancées — Les secrets d'un code pythonique
entwanne — @entwanne — Un zeste de Python — La POO en Python — Notions de Python avancées — Les secrets d'un code pythonique
entwanne — @entwanne — Un zeste de Python — La POO en Python — Notions de Python avancées — Les secrets d'un code pythonique
entwanne — @entwanne — Un zeste de Python — La POO en Python — Notions de Python avancées — Les secrets d'un code pythonique
entwanne — @entwanne — Un zeste de Python — La POO en Python — Notions de Python avancées — Les secrets d'un code pythonique