Étant donné que cela fait bien longtemps que l'on a pas vu un topic d'exo sur ce forum. Me voici aujourd'hui avec un petit sujet de difficulté intermédiaire (voire avancée), pour vous faire creuser vos méninges et éventuellement en découvrir plus sur le fonctionnement de votre langage préféré.
Autant le dire tout de suite, cet exercice va surtout vous faire travailler sur les décorateurs.
Pour ceux qui n'auraient pas encore apprivoisé ces sales bêtes, vous pourrez vous diriger vers le chapitre sur les décorateurs du tutoriel Python officiel, et, si vous voulez approfondir la notion de décoration, (parce qu'un peu de pub ne fait jamais de mal) vous trouverez un peu de lecture supplémentaire avec mon mini-tuto sur le pattern Decorator en Python.
Objectif
L'objectif de cet exercice est de créer un décorateur qui va vous aider à visualiser ce qui se passe à l'exécution de votre programme. Grosso-modo, il va servir à afficher le déroulement du programme en termes d'appels de fonctions.
Ceci peut s'avérer être une aide visuelle extrêmement utile lorsque vous maintenez un gros programme et que vous avez du mal, au bout d'un moment, à visualiser ce qui se passe réellement à l'intérieur, simplement en lisant le code.
Exemple
Imaginons le petit programme suivant (qui n'a strictement aucun intérêt en lui-même, m'enfin...), comprenant des appels de fonctions imbriqués.
def addition(a, b):
return a + b
def fonction1(a, b):
c = addition(a, b)
return a * b + c
def fonction2(a, b):
c = addition(a, b)
d = fonction1(a, b)
return addition(c, d)
fonction2(b=2, a=3)
Le but ultime de cet exercice est de créer un décorateur que l'on pourrait appliquer à ces 3 fonctions pour que lorsque l'on exécute le programme, celui-ci décrive son propre déroulement dans la console, comme ceci :
Bien évidemment, il est absolument interdit de tricher en modifiant le corps des fonctions : la seule chose que vous avez le droit de faire est de leur appliquer un décorateur ! Rassurez-vous cependant, j'ai décomposé cet exercice en plusieurs étapes, de manière à ce que vous ne soyez pas perdus dès le départ.
Énoncé
1/ Créez un décorateur qui affiche ">" avant d'exécuter la fonction décorée, puis "<" après son exécution.
Exemple
def decor(fonction):
# ...
# le code de votre décorateur ici
pass
@decor
def addition(a, b):
return a + b
addition(2, b=3)
Résultat :
>
<
2/ Modifiez ce décorateur pour qu'il affiche le nom de la fonction appelée avant son exécution, puis son résultat après son exécution, comme ceci :
> addition
< 5
Indice : regardez du côté de l'attribut __name__... Eh oui, une fonction est un objet ! Souvenez-vous-en, ça va vous servir plus tard.
3/ Modifiez ce décorateur pour qu'il affiche les arguments de la fonction appelée.
> addition(2, b=3)
< 5
Indice : vous n'êtes pas encore à l'aise avec la notation "*args,**kwargs" ? C'est le moment de vous y coller !
4/ Modifiez ce décorateur pour gérer l'indentation.
Lorsqu'une fonction est appelée à l'intérieur d'une autre fonction, les informations de la fonction appelée sont indentées par rapport à celles de la fonction appelante.
Indice : Il va falloir vous creuser un peu la tête, mais souvenez-vous qu'une fonction est un objet, qu'un décorateur est une fonction, et que Python accepte que l'on ajoute "à chaud" des attributs sur presque n'importe quel objet.
5/ Question subsidiaire : essayez maintenant de faire en sorte que votre décorateur prenne en argument la taille des indentations.
Par exemple, que l'on puisse indenter 2 espaces par 2 espaces, 4 par 4, ... tout cela au choix :
@decor(4) # l'indentation sera de 4 espaces
def addition(a, b):
return a + b
Indice : "Yo dawg ! I heard u like function decorators, so I put a function decorator in ur function so u can return ur decorator while u decorate ur function ! "
Si la question 5 est subsidiaire, c'est simplement parce que son intérêt est purement de vous faire créer un niveau de décoration plus élevé, "pour la beauté du geste". Nous y reviendrons lorsque l'on corrigera cet exercice, mais c'est loin d'être la méthode la plus "intelligente" (dans le sens "simple à utiliser") pour gérer la taille des indentations.
Sur ce, je vous souhaîte un bon courage, et j'attends vos participations avec impatience !
Oui, candide a raison, j'ai oublié de préciser (ça me paraissait évident), que le but de poster ce sujet d'exo sur le forum, c'est de pouvoir vous aider et répondre à toutes les questions que vous pourriez vous poser.
Il suffit de nous dire à quelle étape vous bloquez et ce que vous ne comprenez pas... on ne va ni vous juger, ni vous manger : les décorateurs sont une notion de Python difficile à maîtriser quand on n'a pas encore pris le coup.
Rien que l'étape suivante,(afficher le retour de la fonction), je coince, pour l'instant.
Non, en réfléchissant un peu, ça passe.
def decor(f):
def f1(*args, **named):
print '> %s (' %f.__name__,
for a in args:
print a,
for name, value in named.items():
print ', %s = %s' %(name, value),
print ')'
print '<', f(*args, **named)
return f1
@decor
def addition(a, b):
return a + b
addition(3, b = 5)
La fonction f1 est sensée se substituer à ta fonction addition. Cela signifie qu'elle doit accepter les mêmes arguments, et retourner la même chose...
Désolé, mais je ne comprend pas.
Si je teste, j'obtiens
> addition ( 3 , b = 5 )
< 8
Tu veux dire que f1 dois prendre exactement les mêmes paramètres que la fonction décorée?
Alors, pour afficher les arguments, je dois avoir une couche supplémentaire(je ne trouve pas le mot correct. )?
Ce que je veux dire, c'est qu'avec ton code, en faisant ceci :
@decor
def addition(a, b):
return a + b
res = addition(3, 5)
print "la somme de 3 et 5 est", res
res va valoir None, parce que le décorateur, bien qu'il l'affiche lui-même, ne pense pas à transmettre le résultat de l'addition : il est un intermédiaire invisible entre ton code et le code de la fonction addition, il intercepte l'appel de fonction, rajoute un peu de comportement, mais tout ça en faisant au moins tout ce que fait la fonction qu'il décore. Là, en l'état, tu oublies de retourner le résultat de la fonction décorée, ce qui est ennuyeux.
def decor(f):
def f1(*args, **named):
print '> %s (' %f.__name__,
for a in args:
print a,
for name, value in named.items():
print ', %s = %s' %(name, value),
print ')'
print '<', f(*args, **named)
return f(*args, **named)
return f1
@decor
def addition(a, b):
return a + b
res = addition(3, 5)
print "la somme de 3 et 5 est", res
salut,
j'ai pas pigé grand chose, en fait ce qui m'arrête c'est que je n'arrive pas à comprendre l'utilité même des décorateurs. A part peut-être pour les class singleton ...
Même là encore, dans l'exemple donné (http://www.siteduzero.com/tutoriel-3-323958-les-decorateurs.html#ss_part_3) je ne comprend pas comment la variable "instances" survit à chaque instanciation ... ?
@GurneyH : voilà
Maintenant, tu arrives au plus casse-tête, l'indentation.
@josmiley :
L'intérêt de créer un décorateur en Python (enfin, un décorateur statique avec la syntaxe @truc, c'est qu'il te permet de rajouter un comportement ou une caractéristique à une fonction ou une classe sans en modifier le code-source.
Par exemple dans cet exercice, si tu maintiens un gros programme dont l'exécution est complexe et que tu dois le débugger, tu peux vouloir afficher ce qui se passe réellement pendant son exécution, temporairement, le temps de comprendre : au lieu de modifier toutes tes fonction pour y rajouter des print dans tous les sens (que tu risques d'oublier par la suite), tu peux simplement leur coller un décorateur au tout début de leur définition, ce qui va te permettre de faire la même chose, très rapidement, facilement et proprement : c'est une aide précieuse dans le sens où le comportement que tu rajoutes ne change pas les fonctions en profondeur (elles feront toujours le même travail), et que le code source reste très lisible (quand tu veux retirer tes décorateurs, une simple recherche dans le document sur "@nom_du_decorateur" d'indique tous les endroits où tu l'as appliqué, ce qui est plus simple qu'une recherche sur "print", par exemple).
Dans beaucoup de cas, il s'agit de sucre syntaxique, mais on peut imaginer, par exemple, que tu veuilles créer un module qui ne va pas forcément effectuer un travail "métier" sur le programme, mais plus adapter les fonctions existantes du programme à un fonctionnement précis.
On pourrait imaginer, par exemple (je n'y ai pas réfléchi sérieusement, mais ça doit pouvoir se faire), que tu veuilles optimiser en vitesse l'exécution d'un programme en faisant en sorte que certaines fonctions s'exécutent dans un thread séparé du reste du programme mais que tu ne sais pas encore précisément quelles fonctions valent la peine d'être threadées. Dans ce cas, on peut imaginer que l'on crée un décorateur @threadme qui va prendre ta fonction et faire en sorte qu'elle s'exécute dans un thread à part. Ce décorateur effectuerait toutes les opérations "casserole" (le boiler-plate, c'est tout le code rébarbatif qu'il faut taper pour y arriver), ainsi, tu pourrais faire tes essais en appliquant ton décorateur @threadme à différentes fonctions et constater le résultat. Au lieu d'avoir à réécrire et ré-effacer une dizaine de lignes à chaque fois, tu n'as plus besoin de se faire ballader qu'une seule instruction.
Toujours dans ce petit exemple : tu veux optimiser en vitesse, dans ce cas, tu veux examiner le temps que prennent plusieurs de tes fonctions à s'exécuter en fonction de tes modifications (sauf qu'à la fin, quand tu livres le programme, dans son utilisation normale, tu n'as pas du tout besoin de tout le code qui sert à chronométrer), pourquoi pas créer un décorateur @time_me qui fait ce boulot tout seul ? Ça t'évite de rajouter dans ta fonction les variables qui vont bien et le calcul du temps d'exécution ainsi que l'affichage dans la console... tout ceci est géré par le décorateur, et peut être appliqué à toutes les fonctions que tu veux, sans alourdir ton code.
J'espère t'avoir convaincu de leur intérêt.
Pour ce qui est du singleton, c'est simplement parce que la variable "instances" est un "membre de classe" et non un attribut : la variable est attachée à une classe (la classe Singleton) et non à une instance comme le sont les attributs (self.truc). Il existe donc autant de variables instances que de classes Singleton : à savoir une et une seule.
Est-ce que tu pourrais me dire (rapidement) quelles sont les notions à connaître pour aborder ton exercices sur les décorateurs en python? Comme ça je peux les rajouter dans le tableau du topic exercices.
N'ayant pas un tel niveau en python, je ne saurai pas trop dire par moi même quelles sont ces notions...
Je me demandais dans le cadre de cet exercice, existe-t-il un moyen de redéfinir la classe 'function' de Python de telle sorte qu'à la création d'une fonction n'importe où dans le programme, on puisse décorer automatiquement cette fonction ?
L'idée c'est de définir une sorte de métaclasse, mais pour des fonctions en fait.
En revanche pour appliquer un décorateur à toutes les fonctions qui sont chargées, on peut faire un truc crade dans ce genre (python 3, mais fonctionne aussi en python 2) :
def decor(fn):
def wrap(*args):
r = fn(*args)
print("{0} called:".format(fn.__name__), r)
return r
return wrap
def fct1(a, b):
return a + b
def fct2(a, b):
return a - b
def decorate_all(decorator):
d = globals()
for key, elt in d.items():
if type(elt) == type(decorator) and elt != decorator:
d[key] = decor(elt)
print(fct1(3, 2))
decorate_all(decor)
print(fct1(3, 2))
def decor(f):
def f1(*args, **named):
print '> %s (' %f.__name__,
for a in args:
print a,
for name, value in named.items():
print ', %s = %s' %(name, value),
print ')'
print '<', f(*args, **named)
return f(*args, **named)
return f1
@decor
def addition(a, b):
return a + b
res = addition(3, 5)
print "la somme de 3 et 5 est", res
edit:
<code type="python"></code>
Je crois qu'il y a un problème: dans ton code, tu appelle plusieurs fois la fonction décoré.
En gros tu considère qu'une fonction retournera toujours la même chose si on lui passe les même arguments, ce qui est faux! ( pense au module random).
IDENTATION = 0
def decor(nb=4):
def decorateur(fonction):
def fonction_nouvelle(*arg, **kwarg):
global IDENTATION
IDENTATION += 4
print(" " * IDENTATION, ">", fonction.__name__, arg, kwarg)
resultat = fonction(*arg,**kwarg)
print(" " * IDENTATION,"<", resultat)
IDENTATION -= 4
return resultat
return fonction_nouvelle
return decorateur
@decor(4)
def addition(a,b):
return a + b
@decor(4)
def f1(a,b):
c = addition(a,b)
return a * c
@decor(4)
def f2(a,b):
e = f1(a,b)
return e
j'ai un probleme de portée de variable avec IDENTATION (cela explique l'assignement au debut). pour afficher les parametres il y a quuelque chose de speciale a faire ?
def decor(nb=4):
decor.identation = -nb
def decorateur(fonction):
def fonction_nouvelle(*arg, **kwarg):
decor.identation += 4
print(" " * decor.identation, ">", fonction.__name__, arg, kwarg)
resultat = fonction(*arg,**kwarg)
print(" " * decor.identation,"<", resultat)
decor.identation -= 4
return resultat
return fonction_nouvelle
return decorateur
@decor(4)
def addition(a,b):
return a + b
@decor(4)
def f1(a,b):
c = addition(a,b)
return a * c
@decor(4)
def f2(a,b):
e = f1(a,b)
return e
- Edité par Smich74 30 septembre 2013 à 17:28:01
Si c'était facile, tout le monde le ferait.
[Exercice][Moyen-Avancé] Traçage de l'exécution
× 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.
Python c'est bon, mangez-en.