Mot clé static en C
Ce qu’on veut faire ici en Python existe en C :
void compteur(void){
static int c = 0;
c = c + 1;
printf("Appel numéro : %d\n",c);
}
void main(void) {
int i;
for(i=0;i<3;i++){
compteur();
}
}
L’exécution du programme précédent provoque cet affichage :
Appel numéro : 1
Appel numéro : 2
Appel numéro : 3
La variable statique c
est locale à la fonction compteur
mais elle conserve sa valeur entre 2 appels
(grâce au mot clé static
).
En Python, on ne dispose pas de ce mécanisme mais…
Monkey patching Python
Python permet de faire du «monkey patching», c’est à dire de rajouter (ou modifier) des attributs à n’importe quel objet, à la volée. De plus, une fonction est un objet. On va donc rajouter notre variable comme attribut à la fonction :
def compteur():
compteur.c = compteur.c + 1
print("Appel numéro : ", c)
compteur.c = 0
for i in range(3):
compteur()
Ce code a le même effet que le programme C précédent… mais la ligne compteur.c = 0
n’est pas terrible.
On doit la mettre, sinon, l’expression compteur.c + 1
au début de la fonction ne peut pas être évaluée.
On préférerait la mettre avant la fonction compteur, mais ce n’est pas possible puisque pour écrire compteur.c
il faut que la référence compteur
(à la fonction) soit définie.
Bien sûr la ligne ne peut pas non plus figurer telle qu’elle dans la fonction, sinon le
compteur c
serait réinitialisé à 0 à
chaque appel. On peut tout de même arranger un peu le code comme ceci:
def compteur():
if not hasattr(compteur, 'c'):
compteur.c = 0
compteur.c = compteur.c + 1
print("Appel numéro : ", c)
for i in range(3):
compteur()
Cette fois, l’attribut c
est ajouté et initialisé dans la fonction (compteur.c = 0
),
s’il n’existe pas déjà. Lors du second appel à la fonction compteur
, puisque l’attribut c
existe déjà, on ne fait
rien de spécial…
Avec un décorateur ?
On peut aussi proposer une solution avec décorateurs:
def static(dict_var_val):
def staticf(f):
def decorated(*args, **kwargs):
#print("Appel decorated", args, kwargs)
f(*args, **kwargs)
for var, val in dict_var_val.items():
setattr(decorated, var, val)
return decorated
return staticf
Le décorateur, ici nommé static
est un peu technique (précisément static
est une fonction
qui renvoie un décorateur…). Voilà quelques éléments pour aider à sa compréhension :
la fonction static
prend en paramètre un dictionnaire (les variables statiques qu’on veut utiliser
avec leur valeur initiale). Cette fonction (static
) renvoie un décorateur (staticf
) et ce décorateur prend en
paramètre une fonction (la fonction à décorer f
) et renvoie la fonction decorated
qui contient juste
un appel à la fonction f
avec ses paramètres, et qui est «monkey patchée» :-) par la boucle contenant
setattr(...)
.
Le décorateur est très simple à utiliser, comme c’est souvent le cas avec les décorateurs (l’effort est fait au moment de la conception) :
@static({'c': 0})
def compteur():
compteur.c += 1
print(compteur.c)
for i in range(3):
compteur()
On peut avoir plusieurs variables statiques:
@static({'c': 0, 'p': 1})
def compteur2():
compteur2.c += 1
compteur2.p *= 2
print("c, p:", compteur2.c, compteur2.p)
for i in range(3):
compteur2()
L’exécution donne :
c, p: 1 2
c, p: 2 4
c, p: 3 8
Notons au passage que, si l’on écrit des décorateurs, il est avantageux de regarder du côté
de functools.wraps
, lui-même un décorateur, qui permet de préserver les docstrings (et d’autres informations)
dans les fonctions décorées.