Why this matters
Counting, adding to a running total, and moving to the next item are the three most common jobs in a program. Almost every loop you will write in the loops module uses one of them. The operators that do these jobs are short, which makes them easy to use and easy to misread.
Python does not have the ++ and -- operators that many other languages use for counting. Writing x++ is a syntax error in Python. Counting is done with x += 1, and a separate tool, the walrus operator :=, covers the cases where you need the new value inside a bigger expression. This lesson shows each tool, the order in which Python runs them, and the mistakes that are easy to make.
Reading other people's code is a daily job for a programmer. A reader who can trace each line, writing down the value in every name after every step, can follow any of it. A reader who guesses will miss the one line that matters. The method in this lesson is to trace, not to guess.
Start from the first principle: a name points at a value
A variable is a name that refers to a value. Think of a sticky note with a name on it, stuck onto a jar. The jar holds the value. Writing count = 3 creates the name count and sticks it to a value 3. Python does not ask you to declare the type first, so the same name can later point at a different kind of value.
An assignment attaches a name to a value. The assignment operator is a single equals sign, =:
Read = as "make the name on the left refer to the value on the right". It does not mean "is equal to". That meaning belongs to ==, which you will meet in the next lesson. In count = 3, Python does not ask a question. It does an action: it makes count refer to 3.
Python always does the same steps for an assignment. It works out the right side completely first. Then it attaches the name on the left to the result. The old value of the name, if there was one, is no longer referred to by that name. This order explains almost everything in the rest of this lesson.
Here is a program that shows the steps with a name that changes:
The right side count + 1 is worked out first, using the old value 3, giving 4. Then the name count is attached to 4. The line prints 4. The name no longer refers to 3.
Plain assignment and chains
A value can be given to several names in one statement. Python reads a chained assignment from left to right, and every name refers to the same value:
The output is:
7 7 7Python first evaluates the right side, 7. Then it attaches a, then b, then c to that value. Chains like this are common when you set several names to the same starting value. Many programmers avoid long chains because they are hard to read, but you must be able to read them when you meet them.
One detail matters later in the course. The names all refer to the same object, not to copies. For numbers this does not show, because a number cannot be changed in place. For lists it does, and you will see that in the arrays module.
Augmented assignment: a calculation and a store in one
Writing count = count + 1 is so common that Python has a shorter form. An augmented assignment combines an operator and the assignment sign. The Python name for this is augmented assignment, and many people call it a compound assignment. The table lists the forms for arithmetic.
| Form | Means the same as |
|---|---|
x += 5 | x = x + 5 |
x -= 5 | x = x - 5 |
x *= 5 | x = x * 5 |
x /= 5 | x = x / 5 |
x //= 5 | x = x // 5 |
x %= 5 | x = x % 5 |
x **= 5 | x = x ** 5 |
Here is a program that uses several of these forms on one name. Predict the value after every line before you run it.
The output is:
15
12
24
6
2Trace the last two lines to be sure you have the rule. After x //= 4, the value is 24 // 4, which is 6. Then x %= 4 gives 6 % 4, which is 2. Each line reads the value that the line before left in the name, so the lines build on each other.
Now try /= on the same starting point:
The output is:
6.0
<class 'float'>The name x now refers to a float, not an int. The /= form uses true division, which always gives a float. If you want the value to stay a whole number, use //=.
What the type does when the value changes
In some languages, a compound assignment converts the result back to the type of the variable, and the decimal part is cut off without a message. Python does not do that. The name simply refers to the new value, and the new value has its own type:
The output is:
13.5 <class 'float'>The name n now refers to the float 13.5. Python changed the type of the value, and it did not cut off anything. This is a difference you must remember when you read code from other languages, where the same line would store 13 in an int variable.
Python also refuses to mix types that have no meaning together. A string is text, and an int is a number. The augmented form does not convert between them:
The output is:
Traceback (most recent call last):
File "/path/to/main.py", line 2, in <module>
letter += 1
TypeError: can only concatenate str (not "int") to strPython has no separate character type. A one-letter text value is a string of length 1. To get the next letter, convert it to a code number with ord, add 1, and convert back with chr. The function ord("a") is 97, and chr(98) is "b":
The output is:
b
97 bText has its own rules, which you will meet in the strings module. For now, notice that += on a string joins the text, as in s = "pa" followed by s += "ck", which makes s equal to "pack".
Counting: Python has no ++ or --
Python has no increment operator. Each of the following lines is an error or does nothing:
The output is:
5The line ++x is not an increment. Python reads it as the plus sign applied twice, and plus applied to a number does nothing, so x stays 5. The line prints 5, and no error is shown. This is one of the reasons to use x += 1 for counting, because it says what it does.
The line x++ is a syntax error:
The output is:
File "/path/to/main.py", line 2
x++
^
SyntaxError: invalid syntaxA syntax error means Python cannot read the line at all. The program does not run. The caret points at the place where the reading stopped.
For a plain counter, write count += 1:
The output is:
2Each += 1 adds one to the value the name refers to, and the name then refers to the new value.
Postfix and prefix: the old value or the new value
Some languages have two forms of increment. The postfix form gives the old value and then adds 1. The prefix form adds 1 first and gives the new value. Python does not have either form as an operator, but it has the same two ideas written differently.
To get the old value, save it before the change:
The output is:
old: 5 x: 6To get the new value inside a bigger expression, use the walrus operator :=. It attaches a name to a value and also gives the value back, so it can sit inside an expression. The parentheses are needed when the walrus is inside another expression:
The output is:
new: 6 x: 6The walrus form is the prefix idea. It changes the name first, then gives the new value. Use it only when the value is needed inside the same expression, because a plain x += 1 line is easier to read.
It helps to picture the steps for each form. For the save-first form, Python reads x and keeps the value 5 in old. Then it adds 1 to x. For the walrus form, Python adds 1 to x, attaches x to the new value 6, and gives 6 to new. Both leave x at 6 at the end. The only difference is which value is available in the middle.
The order of evaluation: a worked trace
Python evaluates an expression with several operators from left to right, according to precedence. An augmented assignment reads the name on its left first. Then it works out the right side. Then it adds the two values and attaches the name to the result. This is the most surprising rule in this lesson, so the next example traces it step by step.
The output is:
7Trace it one step at a time. Write down the value of i after each step.
| Step | Expression | What happens | Value of i after | Result so far |
|---|---|---|---|---|
| 1 | i += | Python reads i first, which is 1, and keeps it | 1 | saved value 1 |
| 2 | (i := i + 1) | i becomes 2, and the walrus gives 2 | 2 | 2 |
| 3 | (i := i + 2) | i becomes 4, and the walrus gives 4 | 4 | 2 + 4 = 6 |
| 4 | i = 1 + 6 | the saved 1 plus the right side gives 7 | 7 | i is 7 |
The saved value in step 1 is the key. If Python had read i again after the right side ran, it would have used 4 and printed 10. The rule is that the left name is read before the right side is worked out.
Plain assignment works differently. In x = expression, the name on the left is not read at all. It is only attached to the result.
A game score, traced step by step
Here is a small model of a game, so you can see how several assignments work together. The player gets 10 points for each coin. Two coins are collected. A combo bonus doubles the score. Then a gem adds 5 points. The program keeps two names: score and coins.
The output is:
score: 45 coins: 2Each line reads the names as they are left by the line before it. The table below records both names after every line. Fill it in yourself first, then compare.
| Line | What the line does | score | coins |
|---|---|---|---|
| before any line | both names start at zero | 0 | 0 |
score += 10 | 0 + 10 | 10 | 0 |
coins += 1 | 0 + 1 | 10 | 1 |
score += 10 | 10 + 10 | 20 | 1 |
coins += 1 | 1 + 1 | 20 | 2 |
score *= 2 | 20 * 2 | 40 | 2 |
score += 5 | 40 + 5 | 45 | 2 |
The final state is a score of 45 and two coins.
Now the mistake this example is designed to expose. Suppose the programmer wanted to double the score and then add 5 in one line, and wrote score *= 2 + 5. The augmented operator takes the whole right side first. So the line means score = score * (2 + 5), which multiplies by 7. Starting from 40, the result is 280, not 85. If you meant to multiply and then add, write the plain form score = score * 2 + 5, because an augmented operator always treats its whole right side as one group.
This is the same rule you saw in the arithmetic lesson. Brackets decide the grouping, and an augmented operator always groups its whole right side first.
Integers do not overflow, but fixed sizes do
An augmented assignment uses the same arithmetic as the plain operator. In Python, int values grow as needed, so a running total never wraps around:
The output is:
4000000000The arithmetic lesson explains the fixed-size case. If a value must fit in 32 bits, the ctypes module gives a type that keeps only the low 32 bits of the value:
The output is:
-294967296The sum is 4000000000, which does not fit in 32 bits. The fixed-size type keeps only the low 32 bits, so the stored value wraps to a negative number. Python itself did not fail. Choose a fixed-size type only when a file format, a protocol or a database column needs one.
Reading an assignment in code you did not write
When you open a file someone else wrote, you will meet long lines of assignments. You can read any one of them with the same four steps.
- Find the name on the left of
=. That is the name that changes. - List every name on the right side, and write down the value each one refers to right now.
- Work out the right side with those values, using the rules of precedence. Remember that
*,/and//come before+and-. - Attach the name on the left to the result, and note the new value for later lines.
Try this on a discount line from a shop program:
The output is:
93Step 1: the name is total. Step 2: total is 100, discount is 15. Step 3: discount // 2 is 15 // 2, which is 7, because the part after the point is dropped. Then 100 - 7 is 93. Step 4: total becomes 93. A reader who meant true division would have written discount / 2, which gives 7.5, and would have ended with 92.5. The line is simple, and the mistake is still easy to make.
Augmented lines follow the same steps, with one addition. Step 2 must also include the left name itself, because the augmented form reads the name before it attaches the new value.
Swapping two values
An interview question often asks you to swap two values. In Python the answer is short, because a tuple assignment (values separated by commas, such as q, p) reads both right-hand values before it attaches either name:
The output is:
p is now 9 q is now 3The right side q, p is built first, as the pair (9, 3). Then p is attached to 9 and q is attached to 3. No temporary name is needed.
Many languages need a third name for the swap, because the first assignment would overwrite one of the values before it could be copied. The same idea works in Python if you write it out:
The output is:
p is now 9 q is now 3If you had written p = q then q = p, the second line would copy the new value of p, which is 9, back into q. Both names would refer to 9, and the 3 would be lost. The tuple form avoids that problem because the right side is built before any name changes.
The arithmetic trick a = a + b, b = a - b, a = a - b also works for integers in Python, because Python integers never overflow. It does not work for every type, such as text, so the tuple form is the one to use.
The ticket counter
A very common real use of "give the next number and move on" is a ticket counter. Python has a standard tool for this, itertools.count, which gives an endless sequence of numbers. Each call to next returns the next one:
The output is:
Next: 100
Next: 101
Next: 102The first call gives 100, and the counter moves on to 101. Each later call gives the next number. The counter starts at the number you give to count. The itertools module has more tools, and the Python documentation lists them.
If you want to write the same logic by hand, save the old value first:
The output is:
Next: 100
Counter now at: 101The mistakes that look like progress
Now the most important trap in this lesson. Look at this program and predict its output:
The output is:
5
5The first line does nothing useful. The statement x = x reads the value 5 and attaches the name to the same value again. The programmer may have meant to add one, and the bug is hidden because the program still runs.
The second line is a different error. y =+ 5 is not an augmented assignment. It is two operators: = and then unary +. It stores positive 5 in y, and the old value of 5 is simply replaced. Python does not complain, because the code is legal. You cannot see the error by reading the output alone, so keep the form += in mind.
The Python version of the Java trap x = x++ is a syntax error, not a silent bug, because Python has no ++:
The output is:
File "/path/to/main.py", line 2
x = x++
^
SyntaxError: invalid syntaxCommon mistakes and the messages you will see
Some of these mistakes give messages before the program runs, and others give wrong answers with no message. Learn to read both kinds.
Mistake 1: a space inside the augmented operator
The output is:
File "/path/to/main.py", line 2
x + = 5
^
SyntaxError: invalid syntaxThe two symbols of += must touch. With the space, Python sees x + followed by = 5, and = cannot start an expression. The caret points at the = that caused the problem. Remove the space to make it +=.
Mistake 2: an increment on the left of an assignment
The output is:
File "/path/to/main.py", line 2
x++ = 5
^
SyntaxError: invalid syntaxThe left side of an assignment must be a name, or another place that can hold a value, such as a list item. x++ is not valid Python at all, so there is no place for the value to go.
Mistake 3: using a name before it has a value
The output is:
Traceback (most recent call last):
File "/path/to/main.py", line 1, in <module>
print(count)
^^^^^
NameError: name 'count' is not defined. Did you mean: 'round'?Python cannot read a name that has never been given a value. The message says count is not defined, and it may suggest a similar name. A counter must start with a value, such as count = 0, before count += 1 can run. The same error appears with count += 1 on its own, because the augmented form reads the name first.
Mistake 4: a condition that assigns
The output is:
File "/path/to/main.py", line 2
if x = 5:
^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?The if statement needs a test, and x = 5 is an assignment, not a test. Python gives a hint that names the two operators you probably meant. Use == to compare, or := if you really want to assign inside the test. The equality operator is covered in the next lesson.
Mistake 5: a constant that is still changed
Python has no final keyword. A name that should never change is written in capital letters by convention, and the typing.Final marker tells type checkers that the name must not be reassigned. The Python interpreter does not enforce it while the program runs:
The output is:
4The program runs and prints 4. A type checker tool reports the reassignment as an error before the program starts. If the value must never change, run a type checker such as mypy on the code before you run it.
Mistake 6: confusing the start and the end of a loop
This one is more of a logic error. When a counter is used as an index into a list, reading it before or after the increment picks a different element. The arrays module covers indexes, and you will see the bug there. The fix is to decide which value you want and write the step that gives it. Save the old value before the change, or change first and read after.
Debugging a counter that never changes
Here is a realistic bug. A student writes a program that should count how many numbers are positive. The count always prints 0. The code looks like this:
The output is:
0Read the line inside the if carefully. It copies positives into itself, so nothing changes. The programmer meant to write positives += 1. This kind of bug is easy to write when you are copying an earlier line and forget to change the right side. The loop runs, the test runs, and the name never changes.
Use this method to find it. First, print the name right after it should have changed. Second, look at the line that should change it and ask what the right side is. Third, check the left side. In this case the left side and the right side are the same, which is the sign of a copy-paste mistake. Adding a print line after each assignment makes the problem visible in a few seconds.
The fixed version adds one to the count for each positive value:
The output is:
2Variations and edge cases
The augmented operators work on several types, and each type has its own rules.
- Integers.
+=and-=work onintvalues, and the result is always exact, because Python does not overflow. - Floats.
+=works on afloat, sox = 1.5followed byx += 1gives2.5. - Division.
/=always gives afloat. Use//=to keep anintresult. - Text.
+=joins strings. The rule is the same as for+in the arithmetic lesson: when both sides are text, the result is text. - Lists.
+=on a list adds the items of the right side to the list. You will see this in the arrays module. - Fixed sizes. A value in a fixed-size type, such as
ctypes.c_int8, wraps when it goes past its limit. The value 127 plus 1 becomes -128 in an 8-bit type. - Side effects. The walrus operator changes a name while the expression is being evaluated. It is useful, but code that uses it heavily is hard to read. Keep it for the places where a new value is needed in the middle of a line.
The ctypes example below shows the 8-bit wrap:
The output is:
-128Interview-style questions
Interviewers often ask for the output of a short snippet. Three patterns come up.
The postfix puzzle. Given i = 0 and j = (i := i + 1) + (i := i + 1), what are i and j? The first walrus makes i 1 and gives 1. The second makes i 2 and gives 2. So j is 3, and i is 2.
The output is:
2 3The augmented puzzle. Given k = 2 and k *= (k := k + 1) + 1, what is k? The saved left value is 2. The right side is (k := 3) + 1, which is 4, and k is 3 at that moment. The result is 2 * 4, which is 8.
The output is:
8The in-place counter. Write a function that counts how many numbers from a list are negative. The answer is a single counter with += 1 inside an if. You will write this once you know conditionals and loops. Know now that the counter is a name, and the answer is the value it refers to at the end.
A good answer to any of these says what each name refers to after each step. Interviewers are not checking whether you memorised a result. They are checking whether you can predict the order of operations.
A closer look at the interview answer
Interviewers do not only want the output. They want to hear how you reason. Here is a good way to answer a question that combines both ideas: "What is the final value of k after k = 1 and k += (k := k + 1) * 2 + k?"
Say the steps out loud. Step 1: the left value of k, which is 1, is saved. Step 2: the right side is worked out from left to right. (k := k + 1) makes k 2 and gives 2. Then * 2 gives 4. Then + k reads k again, which is now 2, so the sum is 6. Step 3: the saved value is added to the right side, 1 + 6, which is 7. The answer is 7.
The output is:
7The reasoning matters more than the number. Say which value is read when, and say why. If you can explain the saved left value, the walrus timing, and the left-to-right order, you have shown the three rules this lesson is about.
Practice
1. (Easy) Start with c = 3. What does print((c := c + 1) * 2) print, and what is c afterwards?
Show the answer
It prints 8, because the walrus makes c 4 and gives 4, and 4 * 2 is 8. Afterwards c is 4.
2. (Easy) Start with x = 100. Using only augmented assignment, turn x into 7, keeping int values all the way. The first operator should be //=.
Show the answer
x //= 15 gives 100 // 15, which is 6. Then x += 1 makes 7. Using /= would give 7.666666666666667, which is a float and not 7.
3. (Easy) After a = 4, a -= 1 and a *= a, what is the value of a?
Show the answer
It is 9. The first line makes a equal to 3, and then a *= a gives 3 * 3, which is 9.
4. (Medium) Predict the output of x = 5, then y = (x := x + 1) + 1, then print(x, y).
Show the answer
It prints 6 7. The walrus makes x 6 and gives 6, and 6 + 1 is 7.
5. (Medium) Predict the output of i = 1, then i += (i := i + 1) + (i := i + 2), then print(i). Then say which rule gives the answer.
Show the answer
It prints 7. The left value of i, which is 1, is read before the right side runs. The right side is 2 + 4, which is 6. Then 1 + 6 is 7. The rule is that an augmented assignment reads its left name before it evaluates the right side.
6. (Medium) Write code that counts the characters in the text "hello" with a counter that goes up by one for each character. Keep the increment on its own line.
Show the answer
The output is 5. The loop runs once for each character, and count += 1 adds one each time. len(text) gives the same answer, but this version shows the counting pattern.
7. (Medium) Explain why x = 10 followed by x =- 3 does not subtract 3 from x.
Show the answer
=- is not an augmented operator. It is = followed by unary -. The statement stores negative 3 in x, so x becomes -3. The augmented form is x -= 3, which gives 7.
8. (Medium) Predict the output of k = 2, then k *= (k := k + 1) + 1, then print(k).
Show the answer
It prints 8. The left k is saved as 2. The right side makes k 3 and gives 3 + 1, which is 4. Then 2 * 4 is 8.
9. (Hard) Write code that puts two numbers, a and b, in order from smallest to largest, with a = 9 and b = 3 as the starting values. Use a tuple swap, and print the two values.
Show the answer
The output is 3 9. The swap happens only when a is larger. The tuple form needs no temporary name.
10. (Hard) Given i = 0 and j = (i := i + 1) + (i := i + 1) + (i := i + 1), find i and j without running the code. Then check your answer.
Show the answer
The three walrus expressions give 1, 2 and 3, and i ends at 3. So j is 1 + 2 + 3, which is 6. Check it with print(i, j), which prints 3 6.
11. (Hard) Explain in two sentences why a = b = c = 0 is valid, and what all three names refer to afterwards.
Show the answer
Python evaluates the right side once, which gives 0, and then attaches a, b and c to that value from left to right. All three names refer to the value 0, and no value is lost.
12. (Hard) A programmer writes count = count++ in a loop and wonders why count never grows. Explain the problem in Python and give a correct line.
Show the answer
Python has no ++ operator, so count++ is a syntax error and the program does not run. Write count += 1 instead. It reads count, adds 1, and attaches the name to the new value.
What you now know
- An assignment works out the right side first, then attaches the name on the left to the result.
=stores a value, and==asks a question. They are different operators.- Augmented operators such as
+=combine a calculation and a store. Unlike some languages, Python does not cut off the decimal part. The name takes the new value and its type. - Python has no
++or--. Usex += 1for counting. The walrus:=gives a new value inside an expression. - An augmented assignment reads its left name before it evaluates the right side, which explains the
i += (i := i + 1) + (i := i + 2)puzzle. x = xleavesxunchanged, and=+is not+=. Both are silent mistakes.- Error messages name the exact symbol, so read the caret carefully.