Reputation: 61498
I have noticed that it's common for beginners to have the following simple logical error. Since they genuinely don't understand the problem, a) their questions can't really be said to be caused by a typo (a full explanation would be useful); b) they lack the understanding necessary to create a proper example, explain the problem with proper terminology, and ask clearly. So, I am asking on their behalf, to make a canonical duplicate target.
Consider this code example:
x = 1
y = x + 2
for _ in range(5):
x = x * 2 # so it will be 2 the first time, then 4, then 8, then 16, then 32
print(y)
Each time through the loop, x
is doubled. Since y
was defined as x + 2
, why doesn't it change when x
changes? How can I make it so that the value is automatically updated, and I get the expected output
4
6
10
18
34
?
Upvotes: 0
Views: 38
Reputation: 61498
Many beginners expect Python to work this way, but it does not. Worse, they may inconsistently expect it to work that way. Carefully consider this line from the example:
x = x * 2
If assignments were like mathematical formulas, we'd have to solve for x
here. The only possible (numeric) value for x
would be zero, since any other number is not equal to twice that number. And how should we account for the fact that the code previously says x = 1
? Isn't that a contradiction? Should we get an error message for trying to define x
two different ways? Or expect x
to blow up to infinity, as the program keeps trying to double the old value of x
Of course, none of those things happen. Like most programming languages in common use, Python is a declarative language, meaning that lines of code describe actions that occur in a defined order. Where there is a loop, the code inside the loop is repeated; where there is something like if
/else
, some code might be skipped; but in general, code within the same "block" simply happens in the order that it's written.
In the example, first x = 1
happens, so x
is equal to 1
. Then y = x + 2
happens, which makes y
equal to 3
for the time being. This happened because of the assignment, not because of x
having a value. Thus, when x
changes later on in the code, that does not cause y
to change.
So, how do we make y
change? The simplest answer is: the same way that we gave it this value in the first place - by assignment, using =
. In fact, thinking about the x = x * 2
code again, we already have seen how to do this.
In the example code, we want y
to change multiple times - once each time through the loop, since that is where print(y)
happens. What value should be assigned? It depends on x
- the current value of x
at that point in the process, which is determined by using... x
. Just like how x = x * 2
checks the existing value of x
, doubles it, and changes x
to that doubled result, so we can write y = x + 2
to check the existing value of x
, add two, and change y
to be that new value.
Thus:
x = 1
for _ in range(5):
x = x * 2
y = x + 2
print(y)
All that changed is that the line y = x + 2
is now inside the loop. We want that update to happen every time that x = x * 2
happens, immediately after that happens (i.e., so that the change is made in time for the print(y)
). So, that directly tells us where the code needs to go.
def
ining relationshipsSuppose there were multiple places in the program where x
changes:
x = x * 2
y = x + 2
print(y)
x = 24
y = x + 2
print(y)
Eventually, it will get annoying to remember to update y
after every line of code that changes x
. It's also a potential source of bugs, that will get worse as the program grows.
In the original code, the idea behind writing y = x + 2
was to express a relationship between x
and y
: we want the code to treat y
as if it meant the same thing as x + 2
, anywhere that it appears. In mathematical terms, we want to treat y
as a function of x
.
In Python, like most other programming languages, we express the mathematical concept of a function, using something called... a function. In Python specifically, we use the def
function to write functions. It looks like:
def y(z):
return z + 2
We can write whatever code we like inside the function, and when the function is "called", that code will run, much like our existing "top-level" code runs. When Python first encounters the block starting with def
, though, it only creates a function from that code - it doesn't run the code yet.
So, now we have something named y
, which is a function that takes in some z
value and gives back (i.e., return
s) the result of calculating z + 2
. We can call it by writing something like y(x)
, which will give it our existing x
value and evaluate to the result of adding 2 to that value.
Notice that the z
here is the function's own name for the value was passed in, and it does not have to match our own name for that value. In fact, we don't have to have our own name for that value at all: for example, we can write y(1)
, and the function will compute 3
.
What do we mean by "evaluating to", or "giving back", or "return
ing"? Simply, the code that calls the function is an expression, just like 1 + 2
, and when the value is computed, it gets used in place, in the same way. So, for example, a = y(1)
will make a
be equal to 3
:
1
, calling it z
internally.z + 2
, i.e. 1 + 2
, getting a result of 3.return
s the result of 3
.y(1)
evaluated to 3
; thus, the code proceeds as if we had put 3
where the y(1)
is.a = 3
.For more about using functions, see How do I get a result (output) from a function? How can I use the result later?.
Going back to the beginning of this section, we can therefore use calls to y
directly for our print
s:
x = x * 2
print(y(x))
x = 24
print(y(x))
We don't need to "update" y
when x
changes; instead, we determine the value when and where it is used. Of course, we technically could have done that anyway: it only matters that y
is "correct" at the points where it's actually used for something. But by using the function, the logic for the x + 2
calculation is wrapped up, given a name, and put in a single place. We don't need to write x + 2
every time. It looks trivial in this example, but y(x)
would do the trick no matter how complicated the calculation is, as long as x
is the only needed input. The calculation only needs to be written once: inside the function definition, and everything else just says y(x)
.
It's also possible to make the y
function use the x
value directly from our "top-level" code, rather than passing it in explicitly. This can be useful, but in the general case it gets complicated and can make code much harder to understand and prone to bugs. For a proper understanding, please read Using global variables in a function and Short description of the scoping rules?.
Upvotes: 2