En gros:
>>> fonction_super_lente(True, '127.0.0.1') # calcule
>>> fonction_super_lente(True, '127.0.0.1') # ne calcule pas, retourne le cache
>>> fonction_super_lente(True, '192.168.0.1') # calcule car les paramètres sont différents
En Python on peut le faire de plusieurs manières, malgré ce que prétend la philosophie du langage.
resultats = {}
def fonction_super_lente(le_faire, sur_quoi):
if not (le_faire, sur_quoi) in resultats:
resultats[(le_faire, sur_quoi)] = # balancer le calcul lent ici
return resultats[(le_faire, sur_quoi)]
Cela marche parceque resultats
est gardé dans une closure et donc accessible dans la fonction. Comme il est défini avant, et que les dictionnaires sont mutables, on a toujours la même référence au même dico à chaque appel. On a juste a vérifier qu’un résultat pour ces param existent (les tuples peuvent être des clés de dictionnaires), et si non, on remplit le cache.
Avantages:
Inconvénient:
def fonction_super_lente(le_faire, sur_quoi, _resultats={}):
if not (le_faire, sur_quoi) in resultats:
_resultats[(le_faire, sur_quoi)] = # balancer le calcul lent ici
return _resultats[(le_faire, sur_quoi)]
Même chose que précédement, mais la différence est que le cache est stocké dans un paramètre. Cela marche car les paramètres sont initialisés à la déclaration en Python, et une seule fois.
Notez le underscore devant le nom de paramètre qui est une convention pour désigner un paramètre qui ne fait pas partie de l’API publique.
Avantages:
Inconvénient:
def fonction_super_lente(le_faire, sur_quoi):
resultats = getattr(function_super_lente, '_resultats', {})
if not (le_faire, sur_quoi) in resultats:
resultats[(le_faire, sur_quoi)] = # balancer le calcul lent ici
function_super_lente._resultats = resultats
return resultats[(le_faire, sur_quoi)]
Cela marche car les fonctions en Python sont des objets. On peut leur rajouter des attributs à la volées comme pour n’importe quel objet :-)
Notez l’usage du troisième paramètre de getattr()
qui nous permet d’avoir une valeur par défaut même si l’attribut n’existe pas encore.
Avantages:
Inconvénient:
Il existe pas mal de versions de décorateurs de mémoization disponibles sur le Net. Ca s’utilise généralement comme ça :
from malib import memoized
@memoized
def fonction_super_lente(le_faire, sur_quoi):
return # le truc normalement
Avantages:
Inconvénient:
Cette technique, de par sa nature, implique de tout stocker en mémoire vive. Donc attention à votre RAM, et vérifiez bien que ça vaut le coup d’échanger la charge CPU contre celle de vos barettes. Ensuite, le bénéfice est d’autant plus grand que le code tourne longtemps. Si c’est un script lancé de nombreuses fois, avec quelques appels à la fonction, le gain est faible: entre chaque initialisation de la VM Python, le cache disparait. Dans ce cas il vaut mieux se tourner vers une solution telle que Redis.
Le cas typique d’usage pertinent est celui d’un daemon faisant des appels à une API WEB: en cachant les résultats, vous économisez une requête HTTP.
]]>