1

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.

Stanislav Kralin
  • 11,070
  • 4
  • 35
  • 58
efw
  • 449
  • 3
  • 16
  • As you understand it, what is the difference between a variable "being a float value" and "being an expression"? – mwchase Oct 06 '17 at 00:16
  • Variables NEVER "refer to an expression". They refer to the *value* of evaluating that expression. So, for example, the moment `root = n/2` is executed, `root` holds a particular numeric value that happens to be half of what `n` holds at that time; there's no ongoing connection with the value of `n`. – jasonharper Oct 06 '17 at 00:27

1 Answers1

1

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.

Matthias Fripp
  • 17,670
  • 5
  • 28
  • 45
  • This is great. Thank you for doing this -- much better illustration than I gave. I'm still trying to understand, however. The new value of 'root' is 3.25 after the first iteration, so therefore there is no expression to put that value into with each subsequent iteration as you suggest "it's possible to perform those steps again with the new value of root", but it's my contention that the new value of root is 3.25 and not (1/2) * (new value of root + (n / new value of root)). In other words, the expression no longer exists -- the value of 'root' has been re-assigned. – efw Oct 06 '17 at 02:34
  • @efw, it sounds like you may be coming from a functional programming background, but Python is more procedural. A Python program just consists of a series of commands that will be executed, like a script. In this case, Python follows those statements (described above), then increments k by 1, then repeats exactly the same statements. So the next time through, it does exactly the same thing I described, but starting with root=3.25 instead of root=4.5. The expression is not an object that gets assigned to root and then discarded; it is a script to be followed to calculate a new value for root. – Matthias Fripp Oct 06 '17 at 05:44
  • @Mathias Fripp Thank you. I think I am understanding my mistake. I'm solving for the variable -- I've been looking at variables in Python through the lens of math, instead of the programming language itself. So the expression in the assignment statement never goes away -- it evaluates to a value that the variable references -- but the expression remains as part of the assignment statement. The variable never references the expression, but rather it references the value that the expression evaluates to. Thanks again for taking the time to put this in greater context so that I could understand. – efw Oct 06 '17 at 17:20
  • @efw Yes, Python (and most programming languages) are not as smart as you're giving them credit for! This particular algorithm takes advantage of the fact that the old value of `root` will be forgotten and replaced by the new one. It is the same as saying `root[k+1] = 0.5 * (root[k] + n / root[k])`, except we keep throwing away the old values and just call the current value `root`. Even if we used that subscripted expression, it would be implemented via concrete steps: each time through the loop, `k` would have a particular value, and the values of `root[k+1]` would get calculated one by one. – Matthias Fripp Oct 06 '17 at 18:28