Mémorisation du kwarg par défaut en Python

Voici comment vous pouvez mémoriser une fonction Python:

def memo_square(a, cache={}): if a not in cache: cache[a] = a*a return cache[a] 

L'accueil est à juste titre peu connu, donc sous la coupe, nous analyserons comment cela fonctionne et à quoi il sert.

Tout d'abord, comment et pourquoi cela fonctionne. memo_square (comme toute autre fonction) est un objet de la classe de fonction, qui, entre autres attributs, a un memo_square.__defaults__ rempli lors de la création de l'objet. Tout d'abord, il contient un dictionnaire vide, comme indiqué dans l'en-tête de la fonction:

 >>> memo_square.__defaults__ ({},) 

__defaults__ est un tuple régulier et vous ne pouvez pas modifier ses éléments. Certes, vous pouvez remplacer l'ensemble des valeurs par défaut en une seule fois, mais uniquement par un autre tuple:

 >>> def test(a=1, b=2): ... print(a, b) ... >>> test.__defaults__ (1, 2) >>> test() 1 2 >>> test.__defaults__ = (', ', '') >>> test() ,  >>> test.__defaults__[1] = '' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment >>> test.__defaults__ = {0: ', ', 1: ''} Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __defaults__ must be set to a tuple object 

Soryan, cet article n'arrivera pas à Picaba. Eh bien, ce n'est pas important. L'important est qu'à l'exception du code très délicat, func.__defaults__ est créé une fois pendant la durée du programme avec tous ses éléments. Un tuple et ses éléments ne seront pas recréés à chaque appel de fonction, ils seront utilisés tant que la fonction existera. Mais pour changer, si les éléments eux-mêmes sont mutables, personne ne les interdit. L'incapacité à travailler avec de tels éléments est l' une des façons les plus courantes de se tirer une balle dans le python . Mais la sauvegarde de valeurs entre les appels de fonction peut être très utile. Après plusieurs appels, memo_square.__defaults__ ressemblera à ceci:

 >>> memo_square(2) 4 >>> memo_square.__defaults__ ({2: 4},) >>> memo_square(5) 25 >>> memo_square.__defaults__ ({2: 4, 5: 25},) >>> memo_square(2) 4 >>> memo_square.__defaults__ ({2: 4, 5: 25},) 

Si la fonction a déjà été appelée pour la même valeur, la valeur est calculée et, par conséquent, le cache n'est pas réapprovisionné. Pour un carré, l'avantage est faible (à proprement parler, pour un carré, l'avantage est négatif, car la recherche dans un dictionnaire est plus coûteuse que la multiplication de deux nombres), mais pour des fonctions vraiment chères, la mémorisation / mise en cache peut être utile. Bien sûr, vous pouvez le fournir en python de plusieurs manières. Voici les alternatives que nous avons:

  • @ functools.lru_cache . Un décorateur du module functools qui se souvient des derniers appels de fonction. Il est fiable et simple, mais il utilise tous les paramètres de la fonction comme clés, ce qui signifie qu'il nécessite une capacité de hachage et ne peut pas remarquer que deux valeurs de paramètres formellement différentes sont équivalentes. Avec la première exigence, tout est clair, sur les fonctions des ensembles, par exemple, vous pouvez oublier. Eh bien, ou lorsque vous appelez, convertissez-les en frozenset. Quant au second, par exemple, j'ai une fonction qui accepte une connexion à la base de données SQL et un numéro en entrée, et effectue quelques manipulations avec les données associées à ce numéro. La connexion peut très bien être déconnectée et rétablie pendant le fonctionnement du programme, et le cache lru_cache s'envolera alors. Mais il sait mettre en cache seulement un nombre limité d'appels (en évitant les fuites de mémoire) et est bien documenté.
  • Cache en dehors de la fonction:

     def square(a): return a**a cache = {} for x in values: if x not in cache: cache[x] = x**x print cache[x] 

    Le sens est le même, mais beaucoup plus lourd. De plus, la variable de cache est visible en dehors de la fonction, bien qu'elle ne soit pas utilisée pour autre chose que sa mémorisation. Le cache pendant la mémorisation avec l'argument par défaut est accessible en externe uniquement via func.__defaults__ , qui sont assez difficiles d'accès par erreur.
  • Videz un objet à part entière avec un cache et faites de sa fonction une méthode. Bon en termes d'architecture et de testabilité, il vous permet de maintenir une logique de mise en cache arbitrairement complexe, mais encore plus encombrante en raison du passe-partout dans le code objet. De plus, il n'est pas clair de quoi hériter et s'il faut hériter de quoi que ce soit, s'il y a plus d'une fonction mémorisable.

La principale chose que cette méthode de mémorisation perd, c'est qu'elle n'est pas très idiomatique. Personnellement, lorsque je suis tombé sur cette décision pour la première fois, j'ai réfléchi quelques minutes à ce qui se passait ici et pourquoi. D'un autre côté, au cours de ces quelques minutes, j'ai commencé à mieux comprendre comment les fonctions Python et leurs arguments sont organisés. Donc, même si vous n'utilisez pas les arguments par défaut (pour la mémorisation ou, par exemple, pour accélérer la résolution de noms ), connaître cette technique est toujours utile pour tout nutritionniste.

Source: https://habr.com/ru/post/fr426341/


All Articles