pythoninterpreterinterpreted-language

Does python reuse repeated calculation results?


If I have an expression that I wish to evaluate in Python, such as the expression for r in the code snippet below, will the Python interpreter be smart and reuse the subresult x+y+z, or just evaluate it twice? I'd also be interested to know if the answer to this question would be the same for a compiled language, e.g. C.

x = 1
y = 2
z = 3
r = (x+y+z+1) + (x+y+z+2)

It has been suggested that this question is similar to this question. I believe it is similar. However, I believe the linked question is less of a 'minimal example'. Also in the linked question there is no ambiguity as to the order of operations, for example, in examples similar to this question, where there is no defined order for operations (mathematically), depending on the order of the individual function calls (which are ambiguous), a worse or better job of optimisation may be done. Consider (abba)(abba)(baa*b), there are nested repeated substrings, and depending on the order and amount of preprocessing many different optimisations could be performed.


Solution

  • You can check that with dis.dis. The output is:

      2           0 LOAD_CONST               0 (1)
                  2 STORE_NAME               0 (x)
    
      3           4 LOAD_CONST               1 (2)
                  6 STORE_NAME               1 (y)
    
      4           8 LOAD_CONST               2 (3)
                 10 STORE_NAME               2 (z)
    
      5          12 LOAD_NAME                0 (x)
                 14 LOAD_NAME                1 (y)
                 16 BINARY_ADD
                 18 LOAD_NAME                2 (z)
                 20 BINARY_ADD
                 22 LOAD_CONST               0 (1)
                 24 BINARY_ADD
                 26 LOAD_NAME                0 (x)
                 28 LOAD_NAME                1 (y)
                 30 BINARY_ADD
                 32 LOAD_NAME                2 (z)
                 34 BINARY_ADD
                 36 LOAD_CONST               1 (2)
                 38 BINARY_ADD
                 40 BINARY_ADD
                 42 STORE_NAME               3 (r)
                 44 LOAD_CONST               3 (None)
                 46 RETURN_VALUE
    

    So it won't cache the result of the expression in parentheses. Though for that specific case it would be possible, in general it is not, since custom classes can define __add__ (or any other binary operation) to modify themselves. For example:

    class Foo:
        def __init__(self, value):
            self.value = value
    
        def __add__(self, other):
            self.value += 1
            return self.value + other
    
    x = Foo(1)
    y = 2
    z = 3
    print(x + y + z + 1)  # prints 8
    print(x + y + z + 1)  # prints 9
    

    If you have an expensive function of which you would like to cache the result, you can do so via functools.lru_cache for example.

    On the other hand, the compiler will perform constant folding as can be seen from the following examples:

    >>> import dis
    >>> dis.dis("x = 'abc' * 5")
      1           0 LOAD_CONST               0 ('abcabcabcabcabc')
                  2 STORE_NAME               0 (x)
                  4 LOAD_CONST               1 (None)
                  6 RETURN_VALUE
    >>> dis.dis("x = 1 + 2 + 3 + 4")
      1           0 LOAD_CONST               0 (10)
                  2 STORE_NAME               0 (x)
                  4 LOAD_CONST               1 (None)
                  6 RETURN_VALUE