📜 ⬆️ ⬇️

Memoization of default kwarg in Python

This is how you can memorize the Python function:

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

The reception is undeservedly little known, so under the cut we will analyze how it works and what it is for.

First, how and why it works. memo_square (like any other function) is an object of the function class, which, among other attributes, has a memo_square.__defaults__ tuple memo_square.__defaults__ is filled in when the object is memo_square.__defaults__ . First, it contains an empty dictionary, as indicated in the function header:

 >>> memo_square.__defaults__ ({},) 

__defaults__ is a regular tuple and its elements cannot be changed. You can, however, replace the entire set of default values ​​at once, but only on a different 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 

Sorian, this article will not fall on Picaba. Well, okay, this is not important. It is important that, with the exception of the very cunning code of func.__defaults__ is created once during the work of the program along with all its elements. The tuple and its elements will not be recreated with each function call, they will be used as long as the function exists. But to change, if the elements themselves are mutable, nobody forbids them. The inability to work with such elements is one of the most common ways to shoot yourself in the foot in python . But in general, storing values ​​between function calls can be quite useful. After several calls, memo_square.__defaults__ will look like this:

 >>> 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},) 

If the function has already been called for the same value, the calculation of the value and, accordingly, the cache replenishment does not occur. For a square, the benefit is small (strictly speaking, for a square, the benefit is negative, because searching the dictionary is more expensive than multiplying two numbers), but for real expensive functions, memoization / caching can be useful. Of course, it can be provided in python in more than one way. Here are the alternatives we have:


The main thing that loses this method of memoization is that it is not very idiomatic. Personally, when I stumbled upon this decision for the first time, I thought for a couple of minutes about what is happening here and why. On the other hand, in these few minutes I began to understand a little better how the Python functions and their arguments are arranged. So even if you do not use default arguments (for memoization or, for example, speeding up the resolution of names ), knowledge of this technique is still useful for any player.

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


All Articles