J'arrive un peu tard mais j'ai commencé à galérer un max sur ces histoires de décorateurs (cause niveau Python médiocre !) avant de trouver un excellent article sur le sujet de Simeon Franklin article. Je le recommande vivement à ceux qui seraient dans mon cas : bien qu'en anglais, il explique bien les mécanismes en oeuvre dans les appels de fonctions, en particulier les 'closures' rarement expliquées dans les cours que j'ai pu voir.
Après ça, on est mieux armé pour comprendre les solutions proposées en particulier pour les exos 2 et 3.
- pour l'exercice 1 (limiteur d'appels), le hic, c'est la mémorisation du(des) compteur(s) d'appel (un seul compteur si on limite une seule fonction décorée, un compteur par fonction si on peut limiter plusieurs fonctions).
Fred1599 solutionne en utilisant un dictionnaire global 'functions' : c'est aussi ce qui m'est venu à l'esprit et ça marche aussi si on remplace le dictionnaire par un seul compteur.
simbilou utilise une variable 'CallLimit.call' qui est un dictionnaire local à CallLimit, donc du scope de la fonction englobant CallLimiter, et ça marche parce qu'on utilise un mutable (dictionnaire 'CallLimit.call'). Si on limitait une seule fonction, donc avec un seul compteur (un int) initialisé dans la fonction englobante CallLimit au lieu de 'CallLimit.call', ça ne marche plus : l'int n'est pas mutable et c'est sa valeur à la définition de la fonction interne qui est
dans la closure : compteur n'est plus accessible en mise à jour dans la fonction interne !
(accessoirement, je n'ai pas compris le pourquoi du test : if "call" not in allLimit.dict: ? c'est superflu ou il y a qq chose qui m'échappe ?)
entwanne stocke le compteur dans un attribut 'remain_call', initialisé à la valeur limite, ajouté à la fonction décorée par setattr(f, 'remain_call', n) dans la fonction 'decorator'. L'attribut de la fonction décorée f reste ainsi accessible de partout y compris dans la fonction interne 'decorated' et le mecanisme marche évidemment aussi bien avec un compteur unique.
A partir de tout ça, voici une 4eme solution voisine de celle de entwanne mais utilisant un attribut de la fonction interne 'wrapper' :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | def limit_decorator(func,n):
def wrapper():
wrapper.counts[func.__name__] -= 1
if wrapper.counts[func.__name__] <= 0:
print(" ... the function {} was called too many times".format(func.__name__))
else : func()
wrapper.counts = {}
wrapper.counts[func.__name__] = n
return wrapper
def a_function():
print ("from 'a' func : I do my job.")
def b_function():
print("hello, this is 'b' func!")
a_modified = limit_decorator(a_function, 3)
b_modified = limit_decorator(b_function, 4)
for i in range(4):
a_modified()
b_modified()
|
En sortie :
| from 'a' func : I do my job.
hello, this is 'b' func!
from 'a' func : I do my job.
hello, this is 'b' func!
... the function a_function was called too many times
hello, this is 'b' func!
... the function a_function was called too many times
... the function b_function was called too many times
|
Voilà, voilà … c'est ce que j'ai compris et j'aimerais avoir l'avis de pros pour savoir si je me trompe. En tout cas, merci à tous pour les exos et des solutions intéressantes.