COMP 202 Foundations of Programming • McGill University, Montreal

Revision sheet: Types, conversion and conditions (COMP 202)

This sheet is not a summary of the first weeks of COMP 202: you have the slides. It answers one question, what loses marks on types, conversion and conditions in an assessment, on the paper midterm where you trace a program by hand as much as on an assignment graded by a script that feeds your program inputs you never tried, and which precise gesture prevents each loss.

The angle of the chapter: nothing here crashes in an interesting way. A program that compares the string '20' to the number 18 stops at once, which is the good case; a program whose cascade tests if mark >= 50 first runs to the end and prints D for every student in the class. The sheet is built around that second kind of bug, the one testing does not reveal because the program never complains.

Mark this sheet as read or add it to your favourites: a free account, no password, keeps your read sheets and favourites from one visit to the next and tells you which chapter to tackle next. Create your space, an email is enough.

The thread of the chapter

The TYPE of a value decides what every operator does to it, and everything that enters a program enters as text. Almost every mark lost on this chapter goes to a program that runs without an error and answers the wrong question: a string compared to a number, two floats tested for equality, a cascade of elif whose first test swallows all the others.

This chapter is part of COMP 202, Foundations of Programming (McGill)

The essentials

The type travels with the value, and input hands you text

  • • Python declares nothing: the type belongs to the VALUE, not to the name. The same name can hold 42 on one line and '42' on the next, and every operator looks at the values before deciding what to do.
  • • input() always returns a str, even when the user types 42. So does a line read from a file. Wrap it in int() or float() before any arithmetic or any numeric comparison; a comparison between str and int raises TypeError, and a str beside an int under + raises TypeError too.
  • • int('12') works, int('12.0') raises ValueError, int(12.9) is 12 and int(-12.9) is -12: int truncates toward zero, while -12.9 // 1 floors to -13.0. When a decimal may arrive, read with float().
  • • / always returns a float, even 10 / 5, which is 2.0. A count stays an int; a measurement is a float. Printing shows the difference: 7 against 7.0.
  • • 0.1 is not exact in base two, so 0.1 + 0.2 prints 0.30000000000000004. Two computed floats are compared with abs(a - b) < 1e-9, never with ==. To DISPLAY two decimals, format at printing with f'{x:.2f}'; do not round the value the program still uses.
operation'42', a str42, an intv + 1TypeError43v + '1''421'TypeErrorv * 2'4242'84v < 5TypeErrorFalsev == 42FalseTrue
The same two characters, 4 and 2, under the same four operators: with the quotes, + 1 crashes and + '1' glues; without them, + 1 adds. The value is identical, only the type changed, and every result did.

On a paper trace, write the type next to every value in your table: '42' with its quotes, 42 without, 42.0 with its point. Half of the trace questions of the midterm are decided by that notation alone.

A condition is a value, and a cascade runs ONE branch

  • • Precedence, from tight to loose: arithmetic, then comparison, then not, then and, then or. So not a == b means not (a == b), and a < b and c or d means (a < b and c) or d.
  • • De Morgan: not (A and B) is (not A) or (not B); not (A or B) is (not A) and (not B). Push a negation inward until no not remains, and the condition becomes readable.
  • • A range is an and, never an or: 0 <= mark <= 100, or mark >= 0 and mark <= 100. The version mark >= 0 or mark <= 100 is true for every number.
  • • An if, elif, elif, else cascade tests top to bottom and runs AT MOST ONE branch; each condition is read as if all those above it had failed. A run of separate if statements runs every branch whose test is true, and the last one to run wins.
  • • A cascade without an else does nothing when every test fails: a variable it was meant to set does not exist, and the next line raises NameError.

Markers give the method mark for the cascade whose order matches its thresholds, and the autograder tests the boundary values you did not: 50 exactly, 0, a negative number, a decimal typed where a whole number was expected.

The rules in table form

Each row reads from left to right: the assumptions, then the result. A red cell is not an answer, it is the finding that the form settles nothing and the instruction to rewrite it. Every case is followed by a worked example.

What each gate lets through

Read a line as: the call in the first column, applied to the value of the second, gives the result of the last. A red cell is not a result: it is an exception, and the program stops there with zero from the autograder.

CallGivenResult
int(s) '12' 12

Example: int('12') + 1 is 13, an int; '12' + 1 was a TypeError.

int(s) '12.0' ValueError raises, program stops

Example: int('12.0') stops the program; a user who types 12.0 where an int was read costs the whole test case.

What to do: Read with float() when a decimal may arrive, then int(float('12.0')) is 12 if a whole number is really needed.

int(x) 12.9 12

Example: int(12.9) is 12 and int(-12.9) is -12: toward zero, not toward minus infinity; -12.9 // 1 is -13.0.

float(s) '3' 3.0

Example: float('3') / 2 is 1.5; float('1e3') is 1000.0, scientific notation is understood.

str(x) 3.0 '3.0'

Example: 'Mark: ' + str(3.0) is 'Mark: 3.0', the zero survives; f'{3.0:.0f}' gives '3'.

int(b) True 1

Example: int(True) is 1 and True + True is 2: bool is a subclass of int.

s + n '4', 3 TypeError raises, program stops

Example: '4' + 3 stops the program, while '4' * 3 is '444' and int('4') + 3 is 7.

What to do: Decide which world the result lives in: int('4') + 3 for a number, '4' + str(3) or f'{4}{3}' for text.

Two things never convert: int() refuses a string with a point, and + refuses a str beside an int. Both refusals are on purpose, so that the error surfaces on the line that caused it.

The mistakes that cost marks

These are the errors I correct most often in session. Each one costs marks on a paper, even when the reasoning behind it is right.

1. Comparing what input() returned to a number

the whole test case on the autograder, since the program stops before printing anything; on a paper trace, the line where you wrote adult

What not to write

“age = input('Age: ') then if age >= 18: print('adult'). The user types 20, so it prints adult.”

What to write

“input returns the string '20'; '20' >= 18 raises TypeError. Write age = int(input('Age: ')), and the comparison is 20 >= 18.”

Why: The value is perfectly right, the string holds exactly what was typed; only its type is wrong, which is why the bug is invisible when you read the code. Python 3 refuses to order a str against an int, and old tutorials written for Python 2 show it working.

2. Gluing a number into a message with +

the output line, and with it the test case that compares your output character by character

What not to write

“print('Total: ' + total) where total is 17.5.”

What to write

“print('Total: ' + str(total)), or better print(f'Total: {total:.2f}'), which shows Total: 17.50.”

Why: + between a str and a float has no meaning, so Python raises TypeError instead of guessing. The f-string is the form to copy: it converts, it formats to the decimals the statement asked for, and it never crashes.

3. Reading with int() when the statement allows a decimal

the whole program on that input: int('1.75') raises ValueError before the first computation

What not to write

“height = int(input('Height (m): ')). The user types 1.75.”

What to write

“height = float(input('Height (m): ')), because a height is a measurement and the autograder types 1.75.”

Why: Your own tests typed 2 and it worked. The grading script feeds every kind of value the statement allows, and int applied to a string with a point stops the program. Read the statement: a count is read with int, a measurement, a price, a mark with a decimal is read with float.

4. Testing two computed floats with ==

the branch that never runs, typically 2 to 3 marks on a trace question and one silent wrong answer in an assignment

What not to write

“total = 0.1 + 0.2, and if total == 0.3: print('exact'). It prints exact.”

What to write

“0.1 + 0.2 is 0.30000000000000004, so the test is False. Write if abs(total - 0.3) < 1e-9: and it prints exact.”

Why: Neither 0.1 nor 0.2 is exact in base two, and their two small excesses add up to the float just above 0.3. A tolerance of 1e-9 is far above the rounding of a few operations, about 1e-16 each, and far below any difference that matters in the problem.

5. Validating a range with or

the validation part of the question, and the program then accepts 250 and -40 without a word

What not to write

“if mark >= 0 or mark <= 100: valid = True, else invalid.”

What to write

“if 0 <= mark <= 100: valid = True. A range needs both conditions at once, so and, or the chained comparison.”

Why: Every number is at least 0 OR at most 100, often both, so the test is always true and nothing is ever rejected. The bug is invisible in testing because the program never complains: the only way to see it is to feed it an impossible mark on purpose.

6. Reading not a == b as (not a) == b

the trace question, usually 2 marks, and it is one of the favourite midterm lines

What not to write

“With a = 2 and b = True, not a == b is (not 2) == True, that is False == True, so False.”

What to write

“Comparison binds tighter than not, so not a == b is not (a == b) = not (2 == True) = not False = True.”

not==ab==notbaPython: not (a == b)meant: (not a) == ba = 2, b = True gives Truea = 2, b = True gives False
Two trees for the same line. Python hangs not above the comparison; the student hangs it under it. With a = 2 and b = True the two readings give opposite answers.

Why: not is the loosest of the three unary and binary boolean words after the comparisons, so it applies to the whole comparison. Whenever you MEAN (not a) == b, write the parentheses: a redundant pair costs nothing, this one saves the question.

7. Writing the thresholds of a cascade in increasing order

the whole question: every mark from 50 up is a D, and there is no else, so a mark of 30 leaves grade undefined and print(grade) raises NameError

What not to write

“if mark >= 50: grade = 'D', elif mark >= 60: grade = 'C', elif mark >= 70: grade = 'B', elif mark >= 85: grade = 'A'.”

What to write

“if mark >= 85: grade = 'A', elif mark >= 70: grade = 'B', elif mark >= 60: grade = 'C', elif mark >= 50: grade = 'D', else: grade = 'F'.”

50607085FDDDD50607085FDCBAif m >= 50 first: every mark from 50 up stops at the first testif m >= 85 first: one band per branch, F in the else
Same four thresholds, two orders. Above, the test at 50 comes first and every band from 50 up is labelled D; below, the test at 85 comes first and each band gets its own letter, F falling to the else.

Why: A cascade stops at the FIRST true test. A mark of 87 satisfies mark >= 50, so it never reaches the test for A. With >=, the thresholds go from the highest down; with <, they go from the lowest up. Either way the order of the tests IS the logic, and the else catches what no band claimed.

8. Writing separate if statements where elif was meant

the answer to the trace, and on an assignment a program that charges a child the adult fare while printing no error at all

What not to write

“if age < 6: fare = 0.0. if age < 18: fare = 2.25. if age < 65: fare = 3.50. A five year old pays 0.”

What to write

“All three tests are true for a five year old, so all three run and the LAST assignment wins: fare is 3.50. Replace the second and third if by elif and only the first branch runs.”

Why: Three separate if statements are three independent questions; each one runs when its test is true, whatever happened above. elif is the word that says: only if the previous tests failed. On a paper trace, write next to each if whether it ran, and you will see the three yes.

9. Confusing = and == on a trace

1 to 2 marks on the trace, and every line that follows if you carried 7 forward

What not to write

“x = 5, then the line x == 7 changes x to 7, so print(x) shows 7.”

What to write

“x == 7 compares 5 to 7, produces False and throws it away: x is still 5, and print(x) shows 5.”

Why: = assigns and == compares, and a comparison written alone on a line is a legal statement that changes nothing. The mirror error, if x = 7:, is a SyntaxError, because Python refuses an assignment inside a condition precisely to stop this confusion.

Which method to choose

Which conversion, which test, according to what the statement hands you

Read what the statement gives you and what it asks you to print, not the name of the chapter

  • If a whole number typed by the user: an age, a count, a year → n = int(input('...')), and int only if a decimal can never arrive

    Example: year = int(input('Year: ')), then year % 4 == 0 works

  • If a measurement, a price, a mark that may carry a decimal → x = float(input('...')); a float also accepts '70' and gives 70.0

    Example: weight = float(input('Weight: ')) accepts 70 and 70.5

  • If two numbers typed on one line, like 3 4 → line.split() first, then convert each piece; int('3 4') raises ValueError

    Example: a, b = int(parts[0]), int(parts[1]) with parts = line.split()

  • If a number to show with a fixed number of decimals → format at printing with an f-string; never round the value the program still uses

    Example: print(f'{17.5:.2f}') shows 17.50 while 17.5 stays 17.5

  • If two computed floats to compare → abs(a - b) < 1e-9, or abs(a - b) < 1e-9 * abs(b) when the values are huge or tiny

    Example: abs((0.1 + 0.2) - 0.3) < 1e-9 is True

  • If a value that must lie between two bounds → a chained comparison, lo <= x <= hi, or two tests joined by and

    Example: 0 <= mark <= 100 rejects 250 and -40

  • If bands separated by thresholds, one label per band → a single if, elif, else cascade; thresholds from the top down with >=, or from the bottom up with <; one else at the end

    Example: if bmi < 18.5, elif bmi < 25, elif bmi < 30, else: 25.0 lands in the third band

    validate first, in its own if, so that the else of the bands never receives an impossible value

  • If a condition that starts with not → push the negation inward with De Morgan until no not remains, then reread it aloud

    Example: not (age < 18 or not has_permit) becomes age >= 18 and has_permit

Nothing in this chapter needs a loop or a list. If your answer to a first-chapter question reaches for one, reread the statement: the expected solution is one conversion and one cascade.

How the answer is expected to be written

A marker ticks steps. Here they are in order, with the concluding sentence expected word for word.

Tracing a program by hand on the midterm

When to use it: The statement gives ten to twenty lines and asks what is printed, or the value of a variable at the end

  1. 1 Draw a table with one column per variable, and write each value WITH its type: '42' with quotes for a str, 42 for an int, 42.0 for a float, True for a bool.
  2. 2 At every input(), write the string exactly as typed, quotes included, and only convert it on the line that calls int or float.
  3. 3 At every condition, write its value, True or False, in the margin, and mark which branch of the cascade runs; for separate if statements, mark each one.
  4. 4 At every print, write the exact characters that appear: 7.0 and not 7 for a float, one line per print, and nothing at all for a comparison written alone on a line.
  5. 5 Reread the final line of the table before answering: the answer is the value in the table, not the one you expected the program to compute.

Concluding sentence

“Line 4: total = 10 / 5, so total holds 2.0, a float. Line 6: total == 2 is True, the branch runs and the program prints 2.0.”

The trap: Converting in your head at the input line: the string '20' stays a string until the program itself calls int, and a comparison made before that line is a TypeError, not a number.

Marking: Typically 1 mark per printed line, and the whole trace lost when a type is changed where the program does not change it.

Writing a cascade the marker and the autograder both accept

When to use it: The statement gives bands with thresholds and a label, a price or a dose per band, and something to refuse

  1. 1 Read every value with the right gate: int for a count, float for anything that can carry a decimal.
  2. 2 Refuse the impossible values FIRST, in their own if, with an and or a chained comparison, and print the message the statement gives for them.
  3. 3 Write one if, elif, elif, else cascade for the bands, thresholds in decreasing order with >= or in increasing order with <, and check that each boundary value falls in the band the statement names for it.
  4. 4 Give the else a real meaning, the last band, and never let it stand for invalid data.
  5. 5 Test by hand, before submitting, every threshold exactly, one value just below it, 0, a negative value, and a decimal.

Concluding sentence

“With the thresholds tested from the highest down, if mark >= 85 catches 85 and above, elif mark >= 70 the marks from 70 to 84, and the else catches everything below 50; the invalid marks were rejected before the cascade.”

The trap: Testing only the values the statement gave as examples. The grading script types 50 exactly and -1, and it is the boundary that decides whether 50 is a D or an F.

Marking: Typically 1 mark for the validation, 2 for the cascade in the right order, 1 for the else, 1 for each boundary the program gets right.

Check before you hand in

Five minutes of checking recover more marks than one more problem started in a hurry.

The typical problem, taken apart

A BMI classifier, traced on 70 kilograms and 1.75 metres

Write a program that reads a weight in kilograms and a height in metres, computes the body mass index, weight divided by height squared, and prints the index with one decimal followed by its category: below 18.5 underweight, from 18.5 to below 25 normal, from 25 to below 30 overweight, 30 and above obese. A weight or a height that is not strictly positive is refused with the message Invalid.

Then trace the program by hand on the inputs 70 and 1.75, and say what it prints on the inputs 90 and 0.

18.52530underweightnormaloverweightobesebmi = 22.970 / 1.75 ** 2 = 22.857..., tested with < against each threshold
The four bands of the statement and the value that 70 and 1.75 produce, 22.9, inside the second one. Each threshold belongs to the band on its RIGHT, which is what a strict < gives.
python
weight = float(input('Weight (kg): '))
height = float(input('Height (m): '))
if weight <= 0 or height <= 0:
    print('Invalid')
else:
    bmi = weight / height ** 2
    if bmi < 18.5:
        category = 'underweight'
    elif bmi < 25:
        category = 'normal'
    elif bmi < 30:
        category = 'overweight'
    else:
        category = 'obese'
    print(f'BMI {bmi:.1f}: {category}')

Step 1

Both values are read with float(): weight = float(input('Weight (kg): ')) and height = float(input('Height (m): ')).

Why

A height is a measurement and the grading script will type 1.75. int('1.75') raises ValueError and the program dies before the first computation; float('70') gives 70.0 and costs nothing.

Step 2

The refusal comes first and stands alone: if weight <= 0 or height <= 0: print('Invalid'), and everything else goes under its else.

Why

Refusing is a separate decision from classifying. If the invalid case were left to the else of the band cascade, a height of 0 would raise ZeroDivisionError on the line above it, and a negative weight would be classified as underweight without a word.

Step 3

bmi = weight / height ** 2. On the trace: height ** 2 is 1.75 * 1.75 = 3.0625, then 70.0 / 3.0625 = 22.857142857142858, a float.

Why

** binds tighter than /, so no parentheses are needed, and / returns a float whatever its operands. Write the intermediate 3.0625 in your table: a marker gives the mark for the value, not for the formula.

Step 4

The cascade tests in increasing order with a strict <: bmi < 18.5 is False, bmi < 25 is True, category = 'normal', and the remaining tests are skipped.

Why

Increasing thresholds with < mirror the statement word for word: below 18.5, then below 25 given that 18.5 was not below. A boundary value goes to the band on its right, 25.0 < 25 being False, which is exactly what the statement says with from 25.

Step 5

print(f'BMI {bmi:.1f}: {category}') shows BMI 22.9: normal. The stored bmi is still 22.857142857142858.

Why

The format rounds the DISPLAY, not the value; round(bmi, 1) before the cascade would have moved 24.96 to 25.0 and changed its band. Two decimals in the f-string, :.1f here, is what the grading script compares.

Step 6

Check with the inputs 90 and 0: weight = 90.0, height = 0.0, the refusal test height <= 0 is True, the program prints Invalid and never divides.

Why

The second input of the statement is there to make you run the invalid branch on paper. A program that divides before validating prints a ZeroDivisionError traceback instead of Invalid, and loses the test case.

The conclusion, written out

“On 70 and 1.75 the program computes 70.0 / 3.0625 = 22.857..., the second test bmi < 25 is the first true one, and it prints BMI 22.9: normal. On 90 and 0 the validation test is true and it prints Invalid.”

The classic mistake on this problem: Reading the height with int(input(...)), which raises ValueError on 1.75, or writing the four bands as four separate if statements, so that a bmi of 17 sets category four times and ends as obese.

Learn by heart

  • • input() returns a str, always. int() or float() before any arithmetic or numeric comparison.
  • • + between a str and a number is a TypeError; str * int repeats; str(x) or an f-string builds the message.
  • • / returns a float, always; int() truncates toward zero, // floors; int('12.0') raises ValueError.
  • • Computed floats: abs(a - b) < 1e-9, never ==. Display: f'{x:.2f}', without rounding the stored value.
  • • Precedence from tight to loose: comparison, not, and, or. not a == b is not (a == b).
  • • De Morgan: not (A and B) is (not A) or (not B); not (A or B) is (not A) and (not B).
  • • A range is 0 <= x <= 100 or an and, never an or. A cascade runs ONE branch, its order is its logic, and it ends with an else.

Frequently asked questions

Why does my Python program crash when I compare input to a number?

Because input always returns a string, even when the user types a number. Comparing the string '20' to the number 18 raises TypeError in Python 3. Convert first: write age = int(input('Age: ')) for a whole number, or float(input(...)) when a decimal may be typed, and compare the converted value.

Why does 0.1 plus 0.2 not equal 0.3 in Python?

Because 0.1 and 0.2 have no exact representation in binary, so Python stores the nearest values, both slightly too large, and their sum lands just above 0.3. Never test computed floats with two equal signs. Test whether the difference is tiny instead: abs(a minus b) smaller than 1e-9 counts as equal for any ordinary quantity.

What is the difference between if and elif in Python?

A chain of if, elif, else runs at most one branch: Python tests the conditions from top to bottom and stops at the first true one. Separate if statements are independent, so every branch whose test is true runs, and the last assignment wins. Use elif when the cases exclude each other, and end the chain with an else so that every input lands somewhere.

How does Python read not a == b?

As not (a == b), which means a is not equal to b. Comparison operators bind more tightly than not, and not binds more tightly than and, which binds more tightly than or. If you mean to negate a alone before comparing, write the parentheses yourself: (not a) == b. The safe habit is to parenthesise anything that mixes not with a comparison.

Why does my grade program give D to every mark above 50?

Because the first test of the cascade is mark >= 50 and a cascade stops at the first true test: 87 satisfies it and never reaches the test for A. With >= the thresholds must go from the highest down, 85 then 70 then 60 then 50; with a strict less-than they go from the lowest up. Add an else for the marks below the last threshold.

Practise it

Corrected exercises: Types, conversion and conditions, COMP 202

A method is proved on a paper, not on a sheet. The set for the same chapter takes each of these traps into a problem, with the solution written out step by step.

  • 10 corrected exercises
  • 100 points
  • 150 minutes
Do the exercises
Next sheet Loops and the patterns they carry

© Ahmed Squalli Houssaini. Revision sheet published at www.letuteurscientifique.ca/en/fiches/comp202-types-conversion-conditions. Free for personal and classroom use; republishing it elsewhere requires written permission (legal notice).

See also

Stuck in COMP 202?

Get in touch for a first session. We go back over the traps of this sheet on your own assignments, then trace midterm programs by hand until the type of every value is obvious to you.

Site by Studio Squalli