DS 208 · Week 2

Types, Control Flow & Functions

Three ideas you'll use in every lab from here: what kind of thing a value is, how to make code choose, and how to stop repeating yourself.

Programming for Data Science · University of the Philippines Cebu

Session Map

Three building blocks

Where We Left Off

From "run a cell" to "write the logic"

Last week you set up Python and ran code others wrote. This week you start writing the decisions — and meeting the errors that come with them.

The mindset shift

An error message is not the enemy. It's Python telling you exactly what it couldn't do.

The rule for today

When a cell breaks, read the last line first. It names the problem.

Words for Today · Parts A & B

Eight words for types and control flow

Last week a variable was a name that holds a value (a piece of data): price = 100. Today’s words are about what kind of value it is, and how code makes choices.

type

The kind of value something is. It decides what you can do with it.

e.g. type(5) reports int; type("5") reports str

integer (int)

A whole number, with no decimal point. Used for counts and positions.

e.g. 21, 0, -3

float

A number with a decimal point. Used for measurements and averages.

e.g. 21.0, 3.5

string (str)

Text, written inside quotes. Digits in quotes are still text.

e.g. "Cebu", "21"

boolean (bool)

One of exactly two values, True or False: the answer to a yes/no question.

e.g. 5 > 3 gives True

operator

A symbol that does something to values: add, divide, compare…

e.g. +, /, ==, >=

condition

A yes/no question in code. Its answer is a boolean, and it decides whether a block runs.

e.g. score >= 60

loop

Code that repeats a block: once per item (for), or while a condition stays true (while).

e.g. for h in hours: runs once per number in hours

Words for Today · Part C

Six words for functions

A function is like a recipe: write the steps once, give it a name, then use it whenever you need it. These words name its parts.

function

A named, reusable piece of work: you give it inputs, it gives back a result.

e.g. len([3, 5, 4]) gives back 3

call

Running a function: its name followed by round brackets, with the inputs inside.

e.g. average([3, 5, 4])

parameter

The name, in the function’s definition, that will hold an input.

e.g. numbers in def average(numbers):

argument

The actual value you put in the brackets when you call the function.

e.g. [3, 5, 4] in average([3, 5, 4])

return

Hand a result back to whoever called the function, and stop the function there.

e.g. return total / count

None

Python’s value for “nothing here”. A function with no return gives back None.

e.g. x = print("hi") leaves x as None

Part A

Types

What kind of thing is this value? Get this wrong and everything downstream breaks.

You already store values in variables (Week 1). This part adds the idea that every value has a type, and the type decides what you are allowed to do with it.

The Core Idea

Python treats 5 and "5" as different things

One is a number you can do maths with. The other is text that happens to look like a number. Most beginner errors are really this one error in disguise.

Every value has a type: the kind of value it is. Python writes types with short names — int for integer, str for string, bool for boolean.

# ask Python what kind of thing each value is values = [5, "5", 5.0, True, [5], None] for v in values: print(type(v).__name__) int str float bool list NoneType
  • values = [5, "5", …]a list: several values in square brackets, in order (more in Week 3)
  • for v in values:a loop: run the indented line once for each value, with v holding the current one
  • print(type(v).__name__)ask for v’s type and print its short name
  • outputint whole number · str text · float decimal · bool True/False · list · NoneType the type of None
The Everyday Types

Six you'll meet constantly

TypeExampleWhat it's for
int5Whole numbers — counts, indices
float5.0Numbers with decimals — measurements, averages
str"5"Text — names, labels, anything typed
boolTrueYes/no — the result of a comparison
list[5]An ordered collection of values
NoneTypeNone"Nothing here" — a deliberate blank
It Fails On Purpose

The error that will greet you 100 times

age holds the text "21", not the number 21. Python refuses to add a number to text — and says so on the last line.

Reading this error

Last line first
TypeError = an operator got a type it cannot work with
The message
“can only concatenate str to str”: + on text means glue together, and it was handed a number
Line number
press Run: the full traceback says line 2 — the print line with age + 1
Check first
the type of each side of the +
age = "21" print("Next year you will be", age + 1) TypeError: can only concatenate str (not "int") to str
Two Honest Fixes

Fix the value, or convert where you use it

A Type Surprise

Two kinds of division, two kinds of answer

Python gives you both. / always makes a float; // throws away the remainder. Pick the one that matches your question.

print(7 / 2) # 3.5 (float) print(7 // 2) # 3 (int, floored) print(7 % 2) # 1 (the remainder)
  • 7 / 2 → 3.5ordinary division; the answer is a float (it has a decimal point) — always, even for 4 / 2
  • 7 // 2 → 3floor division: how many whole 2s fit in 7; the leftover is thrown away
  • 7 % 2 → 1remainder (“modulo”): what is left over. x % 2 == 0 is the test for an even number

Why it bites

"Average score" wants /. "How many full teams of 4?" wants //. Same numbers, different truth.

The Disguise

"10" and 10 look identical — to you

Data read from a file or a form arrives as text, even when it's all digits. Python takes the quotes literally.

"10" + "5" # '105' — glued, not added 10 + 5 # 15 — actually added int("10") + 5 # 15 — convert first
  • "10" + "5"two strings: + glues them into '105'. The quotes around the answer tell you it is still text
  • int("10") + 5convert the text to the integer 10 first, then add: 15

The tell

If a "sum" comes out as two numbers stuck together, one of them is secretly a string.

A Useful Oddity

True is 1, False is 0 — literally

A boolean is True or False (capital letter, no quotes). Booleans are a kind of integer in Python. Strange at first, but it makes counting "how many are true?" a one-liner: sum(passed) adds up the list, counting each True as 1 and each False as 0.

True + True # 2 passed = [True, False, True, True] sum(passed) # 3 — counts the Trues

Handy later

Summing a list of comparisons counts how many rows pass a test — a trick you'll reuse all term.

Quick Check

Tap to reveal

What does type(3 / 1) report?

A · int
B · float
C · str
D · It errors

B — float, it's 3.0.

The single / always produces a float, even when the numbers divide evenly. If you needed the integer 3, that's //.

Quick Check

Tap to reveal

What does "3" + "4" produce?

A · 7
B · "34"
C · "7"
D · a TypeError

B — "34".

+ on two strings glues them together. It only errors when the types disagree (str + int). Same symbol, different job depending on type.

Part B

Control Flow

Making code choose, and making it repeat.

You now know what kind of values you are holding. So far, code has run every line once, top to bottom; this part lets it skip lines (a choice) and repeat lines (a loop).

Making Code Choose

if runs a block only when something is true

A condition is a yes/no question such as score >= 90 (“is score at least 90?”). elif (short for “else if”) and else cover the other cases. Python checks them top to bottom and stops at the first match.

A line ending in a colon : owns the block of indented lines under it (4 spaces in). Indentation is not decoration in Python: it says which lines belong to which choice.

score = 73 if score >= 90: grade = "excellent" elif score >= 75: grade = "good" elif score >= 60: grade = "passing" else: grade = "needs work" print(grade) # passing
  • if score >= 90:73 ≥ 90? False → skip its block
  • elif score >= 75:73 ≥ 75? False → skip
  • elif score >= 60:73 ≥ 60? True → run grade = "passing", then jump past the rest
  • else:runs only when every condition above was False
  • print(grade)not indented, so it always runs: prints passing
A Subtle Trap

The order of the branches changes the answer

Python takes the first branch that's true, then stops. score = 75 is ≥ 75, so it never even checks the later, looser conditions.

score = 75 → "good"

It matches >= 75 first. The >= 60 branch is true too, but we never reach it.

Put the loosest test last

If you swapped the branches so >= 60 came first, everything passing would print "passing" — the tighter grades unreachable.

The Questions You Can Ask

Comparisons produce a bool

OperatorAsksExample → result
==equal?3 == 3 → True
!=not equal?3 != 4 → True
> >=greater (or equal)?5 >= 5 → True
< <=less (or equal)?2 < 1 → False
and / orcombine conditionsx > 0 and x < 10
Doing It To Every Item

A loop saves you writing the same line twenty times

A loop repeats a block of code. A for loop walks a list, running the same block once for each item. Here it keeps a running total (a variable that starts at 0 and grows each time round) and counts the non-zero days.

hours = [3, 0, 5, 2, 8, 0, 4] total = 0 days_worked = 0 for h in hours: total = total + h if h > 0: days_worked = days_worked + 1 print(total, days_worked) # 22 5
  • total = 0start both counters at zero, before the loop
  • for h in hours:repeat the indented block 7 times; h is 3, then 0, then 5…
  • total = total + htake the old total, add this day’s hours, store it back
  • if h > 0:only on days with hours, add 1 to days_worked
  • 22 5output: 22 hours in total, spread over 5 days that had any work
Which Number Is Honest?

Average over what, exactly?

Total hours ÷ days worked and total hours ÷ all days are different claims. Choosing the denominator is a judgement, not a formula.

total / days_worked = 22 / 5 = 4.4

"On days I worked, I averaged 4.4 hours." Answers a question about work intensity.

total / len(hours) = 22 / 7 ≈ 3.1

"Across the whole week, 3.1 hours a day." A different, equally valid claim.

Neither is "right"

They answer different questions. State which one you mean. (len(hours) counts the items in the list: 7.)

The Edge Case

What if nobody worked at all?

If every value is 0, then days_worked is 0, and dividing by it crashes. Real data will hand you this case — so handle it on purpose.

total, days_worked = 0, 0 print(total / days_worked) ZeroDivisionError: division by zero

Reading this error

Last line
ZeroDivisionError: division by zero — you divided by 0, which has no answer
Line number
the traceback says line 2: the division inside print
Line 1
total, days_worked = 0, 0 sets both variables at once

The habit

Ask "what's the empty version of this input?" before you ship the happy path.

A Real Difference

elif stops; separate ifs don't

An if/elif chain runs at most one branch. Three separate ifs each get checked — and can all fire.

# chain: first match wins, done if score >= 90: grade = "A" elif score >= 80: grade = "B" elif score >= 70: grade = "C"

The bug you'll write once

Swap those to three ifs and a 95 passes every test — ending as "C", the last one that ran.

Truthiness

Empty things are False

You don't need == 0 or len(x) == 0. Python already treats empty and zero as False in a condition. Values that count as False are called falsy; everything else is truthy.

names = [] if names: print("we have names") else: print("list is empty") # this runs

Falsy in one place

0, "" (empty string), [] (empty list), {} (empty dictionary, Week 3), None — all False. Everything else is True. So if names: reads “if names has anything in it”.

Counting Loops

When you need the position, use range

Looping over items is common; sometimes you need the position too. An item’s index is its position number, counted from 0. range(n) hands you 0, 1, … n-1 — n numbers, starting at 0. range(start, stop, step) can also start elsewhere and jump by step.

for i in range(3): print(i) # 0, then 1, then 2 # stops BEFORE 3 — off-by-one lives here

The classic trap

range(3) gives three numbers, but the last is 2. Count the starts, not the end.

Position and Item Together

enumerate hands you the index and the item

enumerate is a built-in function you wrap around a list in a for loop. Each time round it gives two things: a counting number (the index) and the item itself. Write two names before in to catch both.

It is like a teacher calling the register: “number 1, Ana; number 2, Ben” — you hear the number and the name together.

scores = [88, 92, 75] for i, score in enumerate(scores): print(i, score) for n, score in enumerate(scores, start=1): print("Student", n, "scored", score)
  • for i, score in enumerate(scores):each time round, i gets the position and score gets the value
  • 0 88, 1 92, 2 75the output: positions start at 0, like every index in Python
  • start=1start counting at 1 instead, for lists meant for people: Student 1 scored 88 … Student 3 scored 75
The Other Loop

while: repeat until something changes

Use for when you know how many times; while when you only know the stopping condition.

balance = 100 while balance > 0: balance = balance - 30 print(balance) # -20
  • while balance > 0:check the condition; while it is True, run the block again
  • balance = balance - 30100 → 70 → 40 → 10 → −20; now −20 > 0 is False, so it stops
  • print(balance)prints -20: the loop only checks at the top, so it can overshoot

The danger

If the condition never turns False, the loop runs forever. Always change something inside it.

A Loop That Builds

Grow a new list as you go

A very common shape: start empty, walk the input, append the ones you want. Here, only the passing scores.

passing.append(s) is a method call: a function that belongs to a value, written after a dot. .append(s) adds s to the end of the list passing. After the loop, passing is [82, 91].

scores = [55, 82, 60, 91] passing = [] for s in scores: if s >= 75: passing.append(s) # [82, 91]

Remember this shape

empty list → loop → conditional append. You'll write it a hundred times.

Quick Check

Tap to reveal

With the grade ladder from earlier, which score prints "needs work"?

A · 60
B · 59
C · 75
D · 90

B — 59.

60 matches >= 60 → "passing". Only a score below every threshold falls through to the else. Boundaries are where off-by-one bugs live.

Break

  Five minutes

Back to the idea that ties the whole course together: functions.

Part C

Functions

A name for a piece of work you do more than once.

You can already make code choose and repeat. You have also been using functions all along — print(), len(), int(). This part shows how to write your own.

Write Once, Use Anywhere

A function is a name for a piece of work

A function is a named, reusable piece of work: you give it inputs, it gives back a result. Define it once with def; call it (run it, by writing its name and brackets) as many times as you like. A docstring is the sentence in triple quotes right under def; it tells the reader what the function does.

A function is a recipe card. def writes the card; calling it cooks the dish with whatever ingredients you hand over; return puts the finished dish in your hands.

def average(numbers): """Mean of a list, or 0 if empty.""" if len(numbers) == 0: return 0 return sum(numbers) / len(numbers) print(average([3, 5, 4])) # 4.0 print(average([])) # 0
  • def average(numbers):define a function called average with one input, named numbers
  • if len(numbers) == 0:if the list is empty… return 0 hands back 0 and stops right there
  • return sum(numbers) / len(numbers)add the values, divide by how many there are, hand the answer back
  • average([3, 5, 4]) → 4.0a call; 12 / 3 = 4.0, a float because / always gives a decimal
  • average([]) → 0the empty case is caught by the if and gives back 0
The Distinction That Trips Everyone

return hands a value back; print just shows it

Why The Guard Was There

Handle the empty case instead of crashing

Remove the if len(numbers) == 0 check and average([]) divides by zero. A guard is an early check that catches a bad or empty input before it can cause trouble. The guard turns a crash into a sensible answer.

Without the guard

average([]) → ZeroDivisionError. The whole program stops.

With the guard

average([]) → 0. The caller keeps running.

The judgement

Is 0 the honest answer for "no data"? Sometimes yes — sometimes you'd rather it complained. (More on that next.)

The Vocabulary

Parameter vs argument

The parameter is the name in the definition. The argument is the real value you pass in when you call it.

def greet(name): # name = parameter return "Hi, " + name greet("Ana") # "Ana" = argument

Why it matters

The same function does different work depending on the argument — that's the whole point of naming it.

The parameter is the blank on a form (“Name: ____”); the argument is what you write in the blank this time (“Ana”).

Stretch Idea

Better a loud error than a quiet wrong answer

Given nonsense input, a function that returns a meaningless number hides the bug. One that raises an error stops you before the wrong number spreads. To raise an error is to stop on purpose with your own message; the error itself is called an exception (more in Week 4).

def average(nums): if not isinstance(nums, list): raise TypeError("expected a list") ... average("hello") TypeError: expected a list
  • isinstance(nums, list)asks “is nums a list?” → True/False; not flips it
  • raise TypeError("expected a list")stop here with our own error type and message
  • ...a placeholder: “the rest of the function goes here”
  • Run it: tracebacktwo File lines: line 6 is the call, line 3, in average is where it stopped. Last line = our message

Principle

Fail fast, fail clearly. A wrong number that looks fine is the most expensive bug.

Find The Bug

Why does this always return 1?

It should count passing scores. But passing = 0 sits inside the loop, so it resets every pass. Only the last item's count survives.

def count_passing(scores): for s in scores: passing = 0 # resets each time! if s >= 60: passing = passing + 1 return passing count_passing([80, 40, 90]) # 1, want 2

The fix

Move passing = 0 above the loop, so it accumulates across items.

Order Matters

Arguments fill parameters left to right

The position decides which value lands in which parameter. Swap them and the code still runs — with the wrong answer.

def divide(top, bottom): return top / bottom divide(10, 2) # 5.0 divide(2, 10) # 0.2 — silently wrong

Guard against it

Name them at the call — divide(top=10, bottom=2) — when the order isn't obvious. These are keyword arguments: name=value, so position no longer matters.

Sensible Defaults

A parameter can bring its own value

Give a parameter a default (a value written with = in the definition) and callers may skip it. Fewer required arguments, same flexibility when you need it.

def greet(name, greeting="Hi"): return greeting + ", " + name greet("Ana") # 'Hi, Ana' greet("Ana", "Hello") # 'Hello, Ana'

One rule

Defaults come after the required parameters — Python insists on it.

Shape Of A Function

Handle the bad case first, then relax

A guard clause deals with the empty or wrong input up top and returns. The rest of the function can then assume all is well. if not nums: reads “if nums is empty” (an empty list is falsy).

def average(nums): if not nums: return 0 # empty case, handled return sum(nums) / len(nums)

Why up top

Deal with the exception first and the happy path reads clean — no nesting, no surprises.

The Lab's Bug, Fixed

Same crash you met at loops — now inside a function

Spot The Bug

Tap to reveal

This should count scores ≥ 75. It always returns 1. Why?

def count_passing(scores): for s in scores: n = 0 if s >= 75: n = n + 1 return n

n = 0 is inside the loop.

It resets to 0 on every pass, so it only ever remembers the last score. Move n = 0 above the loop and it counts them all.

Defensive Functions

Validate, then compute

Before trusting an input, check it. A clear early error beats a confusing crash five lines later — or worse, a wrong answer that never complains.

def percent(part, whole): if whole == 0: return None # nothing to be a % of return 100 * part / whole

The through-line of week 2

Types, control flow, functions — all three meet here: check the type, branch on the edge case, wrap it in a function you can trust.

Quick Check

Tap to reveal

A function's body is just print(sum(nums)) — no return. After x = f([1,2,3]), what is x?

A · 6
B · None
C · [1, 2, 3]
D · an error

B — None.

It printed 6 to the screen, but a function with no return hands back None. That's why x holds nothing usable.

This Week's Lab

Types, choices, and a bug to fix

You'll fix the "21" + 1 error, convert text to a number, reorder the grade ladder, count with a loop, use range and enumerate, give a function a default, and guard a divide-by-zero. ~45 minutes.

How it works: replace each ____, press ▶ Run to see the output, then Check. Each part starts with a short explanation of the words it uses.

Read the last line

Every broken cell is a chance to practise the one skill that matters: reading the error.

Stretch, if you want

Make average() fail loudly on bad input, and explain why that's better than a wrong number.

Recap

Three ideas, three bugs to recognise

Glossary

Week 2 words, one line each

Every new word from today. Week 3 builds on all of them.

Before Next Week

Practice & reading

Next Week

Data Structures & Files

Lists, dictionaries, and reading real files — collections that hold the data you'll spend the rest of the course analysing.

DS 208 · Programming for Data Science