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.
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:
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].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
.