Trouble understanding variables used in while loops

I’m new to Python. While going through an online Python tutorial about while loops, I wrote some code that results in an infinite loop and I just can’t understand why. Here’s the code:

x = 1
powers_of_two = 2**x

while powers_of_two <= 1024:
  print(powers_of_two)
  x += 1

This just returns infinite 2s printed over and over. When I write the code like so, it works as expected and yields the desired returns:

x = 1
powers_of_two = 2**x

while powers_of_two <= 1024:
  print(powers_of_two)
  powers_of_two *= 2

I understand why the latter version works, but I just don’t understand why the first version doesn’t work and return the exact same results. Please, any understanding you can shed on how this is working will be greatly appreciated. Thank you!

Hi Russel,

Programming is not like mathematical formulas, where an equation
defines one variable in terms of the other so that they automatically
change in lockstep with each other.

In programming we refer to = as assignment. It does a one-off
calculation and assigns that value to the variable.

When you have a line like:

powers_of_two = 2**x

that sets powers_of_two to the current value of 2**x, it doesn’t
automatically update every time x changes its value. So if you want the
variable power_of_two to change, you have to explicity recalculate it
each time through the loop.

In the first sample, how does powers_of_two change? When you have an infinite loop, that is usually an important question. There is nothing to change powers_of_two. Also in both samples, x is doing nothing; powers_of_two = 2**1 is the same as powers_of_two = 2 (right?) and then x is not used again (it has no affect in the first example). So if you eliminate x in both and add the necessary calculation into the first sample, the samples are the same.

Programming is not like mathematical formulas, where an equation
defines one variable in terms of the other so that they automatically
change in lockstep with each other.

Well, procedural programming is not like mathematical functions.
Functional languages, by contrast, aim for that model.

That said, Python is a procedural language.

[…]

When you have a line like:

powers_of_two = 2**x

that sets powers_of_two to the current value of 2**x, it doesn’t
automatically update every time x changes its value. So if you want the
variable power_of_two to change, you have to explicity recalculate it
each time through the loop.

And typically you would do that as a function. For example:

def powers_of_two(x):
    return 2**x

That is a static definition of some computation. Nothing happenings
until you call that definition as your procedural programme proceeds.
For example, in your loop you cause that to be calculated by going:

pwr = powers_of_two(x)

which puts the latest result into “pwr” for use.

Cheers,
Cameron Simpson cs@cskk.id.au

Well, procedural programming is not like mathematical functions.
Functional languages, by contrast, aim for that model.

I don’t think that extends to assignment. Here is Haskell. The prompt is
“Prelude>”, the rest of the line is code.

> let a = 100
> let b = a + 1
> b
101
> let a = 200
> b
101

Although it has to be said that Haskell has an extrordinarily
complicated and complex execution model, it distinguishes between
“binding” and “assignment” as two different things, it has three
assignment/binding operators:

a <- 1
a = 1

and apparently >>= which is called “the monadic bind operator”.
Allegedly every line of code in the interactive interpreter is executed
in its own scope. And assignment with <- gives a ludicrously
unintelligible (to me!) error in the interactive interpreter:

> x <- 100

<interactive>:14:6: error:
    • No instance for (Num (IO a0)) arising from the literal ‘100’
    • In the first argument of ‘GHC.GHCi.ghciStepIO ::
                                  forall a. IO a -> IO a’, namely
        ‘100’
      In a stmt of an interactive GHCi command:
        x <- GHC.GHCi.ghciStepIO :: forall a. IO a -> IO a 100

so it’s quite possible I’ve completely missed something and have no
clue about what I am seeing.

In a way, it does, as variables are immutable, so there’s no way to invalidate the relationship established by the assignment.

(assuming you’re keeping a strict functional style, even Haskell allows procedural programming, including mutable state)

Thanks, Steven! Your answer clarified the issue for me right away. I was able to make x change the value of powers_of_two as follows:

x = 0
powers_of_two = int()

while powers_of_two < 1024:
  x += 1
  powers_of_two = 2**x
  print(powers_of_two)

I see now I had to simply make powers_of_two both local to the loop block code, but also global so that the while statement would recognize the variable. I could have initially assigned almost any arbitrary integer to the global variable, but I think assigning it int() was a more palatable solution. Unfortunately the ensuing conversation about assignment in procedural and functional programming languages is over my head. Hopefully someday I’ll be able to understand that, as well. Thanks again!

Sam, thanks for your feedback. The fact that x does nothing in the first example was the basis of my question, I didn’t understand why x wasn’t changing powers_of_two. In the second example, yes of course x does nothing, I just wanted to keep the comparison straightforward so I left it in. Maybe I ought not to have done. Ultimately, I’m aware that none of this is the best way to achieve the desired result. A better way is simply:

powers_of_two = 2
while powers_of_two <= 1024:
  print(powers_of_two)
  powers_of_two *= 2

But I wasn’t looking for the best way to achieve a result, in fact there’s no context here, it’s just an exercise. I was just trying to understand how variables related to/worked within loops, it was purely a conceptual problem.

So you understand now. I hope you do. Regardless of local versus global and what or is not procedural and non-procedural and what or is not assignment, I think the important thing is that in a while statement the condition must (usually) first be false then eventually become true.

I understand. I apologize if my reply sounds like a lecture. I did not know what you did not understand.

If you are going to use an x then the following is another way to do it. So I was thinking your x was for something like this.

for x in range(2, 11):
    print(2**x)