Search code examples
python-3.xfunctionfor-loopnewtons-method

Variable Reassignment in Python-- ontological query -- using Newton Method


I'm reading from Miller and Ranum's book on Algorithms and Data Structures using Python. They use the following example:

def squareroot(n):
    root = n/2
    for k in range(20):
        root = (1/2)*(root + n / root)

    return root 

My question is, the variable 'root' is being reassigned within a for-loop so that with each iteration the value of 'root' within the expression to the right of the assignment operator changes. I'm not sure I understand how this is possible.

Once a function call is made, the 'root' variable outside the for-loop (on line 2) evaluates to a value, which is then referred to by the 'root' variables in the expression of the for-loop block, allowing the expression to evaluate to a single value which is re-assigned to the variable 'root', to the left of the assignment operator, in the for-loop block. At the start of the next iteration, 'root' is no longer n/2, but whatever value the expression in the for-loop has evaluated to. In this case, the variable 'root' has been reassigned a float value and is therefore no longer what it was originally defined -- an expression using 'root' variables.

For example, if we use the function call squareroot(9), 'root' would hold the value of 3.25 after the first iteration, because the expression in the for-loop evaluates to that value. Once the variable 'root', in the for-loop, has been reassigned a single float value, the expression, which originally defined 'root' is destroyed. 'root' has since been redefined as 3.25. 'root', in the for-loop, no longer refers to an expression, but to a single float value. It seems, however, that in this example, the 'root' variable in the for-loop has two meanings following each iteration: it's both a float value and an expression. I don't understand how that can be.


Solution

  • The short answer is that Python doesn't treat the expression as an abstract formula; it treats it as a concrete series of calculations to be performed. Each time through the loop, it performs those calculations with the current value of root, and then uses the result to update the value of root. It might be helpful to look at the actual sequence of operations that Python performs, like so:

    import dis
    def squareroot(n):
        root = n/2
        for k in range(20):
            root = 0.5 * (root + n / root)
        return root
    dis.dis(squareroot)
    

    result:

      2           0 LOAD_FAST                0 (n)
                  3 LOAD_CONST               1 (2)
                  6 BINARY_DIVIDE       
                  7 STORE_FAST               1 (root)
    
      3          10 SETUP_LOOP              38 (to 51)
                 13 LOAD_GLOBAL              0 (range)
                 16 LOAD_CONST               2 (20)
                 19 CALL_FUNCTION            1
                 22 GET_ITER            
            >>   23 FOR_ITER                24 (to 50)
                 26 STORE_FAST               2 (k)
    
      4          29 LOAD_CONST               3 (0.5)
                 32 LOAD_FAST                1 (root)
                 35 LOAD_FAST                0 (n)
                 38 LOAD_FAST                1 (root)
                 41 BINARY_DIVIDE       
                 42 BINARY_ADD          
                 43 BINARY_MULTIPLY     
                 44 STORE_FAST               1 (root)
                 47 JUMP_ABSOLUTE           23
            >>   50 POP_BLOCK           
    
      5     >>   51 LOAD_FAST                1 (root)
                 54 RETURN_VALUE        
    

    The interesting part is the block beginning with 4, which corresponds to the assignment you asked about. This is what happens:

    • load the following onto the stack: [0.5, current contents of root, current contents of n, current contents of root]. During the first iteration (with n=9), the stack will now hold [0.5, 4.5, 9.0, 4.5].
    • divide the second-to-last item on the stack by the last item and put the result on the stack instead: stack is now [0.5, 4.5, 2.0]
    • add the last two items on the stack and put the result on the stack instead: stack is now [0.5, 6.5]
    • multiply the last two items on the stack and put the result on the stack instead: stack is now [3.25]
    • store the last item on the stack (3.25) in the variable root.

    So, as you can see, an expression just represents a sequence of steps to follow. At the end of those steps, the result gets stored to root. Then it's possible to perform those steps again with the new value of root.