Day 2 / 25 Operators, Expressions & Strings 0/0 exercises Exercises ↓

AmouAI Hub/Courses/Programming Fundamentals/Day 2

Week 1 · Python Core Foundations · Day 2

Operators, Expressions & Strings

Yesterday you stored values. Today you compute with them - and you meet the two operators (// and %) that quietly do half the work in every program you will ever write.

Study time
4 hours + exercises
Reading
Think Python, Ch. 2 & 8
Focus
Arithmetic, logic, text

By the end of today you can

  1. Use every arithmetic operator correctly, including //, % and **
  2. Predict the result of any expression by applying operator precedence
  3. Build boolean expressions with and, or, not and explain short-circuiting
  4. State which values Python treats as falsy, and why if x: beats if x == True:
  5. Index and slice strings, including with negative indices and steps
  6. Clean and reshape messy text with string methods

Today's videos

Watch each video, then work the matching sections below. Watching alone will not do it.

2A
Expressions and Operators (120 min)
Arithmetic and integer division · the modulo workhorse · precedence and parentheses · comparison operators · == versus is · and/or/not · short-circuiting · truthiness · augmented assignment.
2B
Strings in Depth (120 min)
Indexing and slicing · the fence-post diagram · immutability and what it forces · the string methods you will actually use · formatting for readable output · two full programs.

1Arithmetic, and the two operators that matter most

Six of the seven arithmetic operators are obvious. The other two, // and %, are the ones you will reach for constantly.

OperatorNameExampleResult
+Add7 + 310
-Subtract7 - 34
*Multiply7 * 321
/True division7 / 32.3333333333333335 (always a float)
//Floor division7 // 32 (whole part only)
%Modulo7 % 31 (the remainder)
**Power7 ** 3343
/ always returns a float

6 / 3 is 2.0, not 2. If you need a whole number, use //. And // floors toward negative infinity: -7 // 2 is -4, not -3.

Modulo is not a niche operator

% gives you the remainder. That one fact solves a surprising number of problems:

modulo.py
n % 2 == 0            # is n even?
n % 5 == 0            # is n a multiple of 5?
total % 60            # leftover seconds after taking out whole minutes
1234 % 10             # 4  - the last digit
1234 // 10            # 123 - everything except the last digit
(i + 1) % 7           # cycle 0,1,2,3,4,5,6,0,1,2... forever

Together, // and % let you take a number apart:

time_split.py
total_seconds = 9045

hours = total_seconds // 3600            # 2
remaining = total_seconds % 3600         # 1845
minutes = remaining // 60                # 30
seconds = remaining % 60                 # 45

print(f"{hours:02d}:{minutes:02d}:{seconds:02d}")
02:30:45

Precedence

Python evaluates in this order, highest first: **, then unary -, then * / // %, then + -, then comparisons, then not, then and, then or.

Precedence stepperinteractive

Type an expression. See exactly how Python groups it, and every reduction in order. Try 2 + 3 * 4 ** 2 % 5 or -2 ** 2.

Press the button.

Try: 2 + 3 * 4 · (2 + 3) * 4 · -2 ** 2 · 2 ** 3 ** 2 · -7 // 2 · 10 - 3 - 2

Parentheses are free

2 + 3 * 4 ** 2 % 5 is legal and correct and nobody reading it is confident. 2 + ((3 * (4 ** 2)) % 5) costs you six characters and removes all doubt. If you had to pause to work out the order, add the parentheses.

2Comparison and boolean logic

Comparisons produce booleans. Booleans combine. That combination is the engine of every decision your programs will make from tomorrow onward.

compare.py
7 == 7      # True   - equal to
7 != 3      # True   - not equal to
7 > 3       # True
7 <= 7      # True

# Chained comparisons read exactly like maths
score = 85
print(0 <= score <= 100)      # True
True
== compares values, is compares identity

== asks "are these the same value?" is asks "are these literally the same object in memory?" They agree often enough to fool you and disagree often enough to waste your afternoon. Rule: use == for everything except x is None.

and, or, not

aba and ba or bnot a
TrueTrueTrueTrueFalse
TrueFalseFalseTrueFalse
FalseTrueFalseTrueTrue
FalseFalseFalseFalseTrue
Truth table builderlive python

Write any boolean expression using the names a, b and c. Every combination is evaluated for you.

python not loaded
Press Build table.

Try: not (a and b) · (not a) or (not b) · a and (b or c)
The first two are De Morgan's law. Build both and compare the columns.

Short-circuiting

Python stops evaluating as soon as the answer is certain. False and anything is False, so the right side is never even looked at.

shortcircuit.py
count = 0
total = 10

# Without short-circuiting this would crash with ZeroDivisionError.
# Because count != 0 is False, Python never evaluates the division.
if count != 0 and total / count > 5:
    print("high average")
else:
    print("no data or low average")
no data or low average

Truthiness

Every value can be used where a boolean is expected. These are the falsy ones, and everything else is truthy:

truthy.py
bool(0)        # False
bool(0.0)      # False
bool("")       # False   - empty string
bool([])       # False   - empty list
bool({})       # False   - empty dict
bool(None)     # False

bool(-1)       # True    - any non-zero number
bool("0")      # True    - a non-empty string, even if it says "0"
bool("False")  # True    - still just text
bool(" ")      # True    - a space is a character
Write if x:, not if x == True:

They are not the same. if x: asks "is x truthy?" and works for any type. if x == True: asks "is x equal to the boolean True?" - which is False for x = "hello". Use the short form.

Augmented assignment

augmented.py
total = 10
total += 5     # same as total = total + 5   ->  15
total -= 3     # 12
total *= 2     # 24
total //= 5    # 4
total **= 2    # 16
print(total)
16

3Indexing and slicing strings

Strings are sequences of characters, and every character has a position. Two positions, actually - and knowing that kills off-by-one errors for good.

Positions count from zero. Negative positions count from the end, starting at -1.

indexing.py
word = "PYTHON"

print(word[0])     # 'P'   first character
print(word[5])     # 'N'   sixth character
print(word[-1])    # 'N'   last character
print(word[-2])    # 'O'   second from the end
print(len(word))   # 6
print(word[6])     # IndexError - there is no position 6
P N N O 6

Slicing: [start:stop:step]

start is included, stop is excluded. Think of the indices as sitting between the characters, like fence posts - that is why word[0:3] gives you three characters, not four.

Slicing playgroundinteractive
leave blank for none

Try: [:3] · [3:] · [:-1] · [-4:] · [::2] · [::-1] · [2:8:3]

slicing.py
s = "PROGRAMMING"

s[0:4]      # 'PROG'   positions 0,1,2,3
s[:4]       # 'PROG'   start defaults to 0
s[4:]       # 'RAMMING'  stop defaults to the end
s[:]        # whole string
s[-4:]      # 'MING'   last four
s[:-4]      # 'PROGRAM'  everything except the last four
s[::2]      # 'PORMIG'  every second character
s[::-1]     # 'GNIMMARGORP'  reversed
s[20:30]    # ''  out of range slicing gives an empty string, no error
Slicing never raises IndexError

s[99] crashes. s[99:200] quietly gives you ''. That asymmetry surprises people - and it is occasionally exactly what you want.

4Strings are immutable

You cannot change a string. You can only build a new one. Once that lands, a whole class of bugs disappears.

immutable.py
name = "amin"
name[0] = "A"       # TypeError: 'str' object does not support item assignment
TypeError: 'str' object does not support item assignment

Every string method returns a new string and leaves the original alone. This is the single most common Day 2 mistake:

reassign.py
name = "amin"

name.upper()          # this computes "AMIN" and throws it away
print(name)           # 'amin'  - nothing changed

name = name.upper()   # THIS is how you keep the result
print(name)           # 'AMIN' 
amin AMIN
Calling a method is not the same as using its result

If a line is just text.strip() with nothing on the left of an =, that line does nothing at all. Python will not warn you.

5The string methods you will actually use

There are dozens. These fifteen cover almost everything you will do this course.

MethodWhat it doesExample
.upper() / .lower()Change case"Hi".upper()'HI'
.title()Capitalise Each Word"amin a".title()'Amin A'
.strip()Remove whitespace from both ends" hi ".strip()'hi'
.replace(a, b)Swap every occurrence"a-b".replace("-", " ")'a b'
.split(sep)Break into a list of pieces"a,b,c".split(",")['a','b','c']
sep.join(parts)Glue pieces back together"-".join(["a","b"])'a-b'
.find(x)Position of x, or -1"hello".find("l")2
.count(x)How many times x appears"hello".count("l")2
.startswith(x) / .endswith(x)Boolean test"cat.py".endswith(".py")True
.isdigit()All characters are digits"42".isdigit()True
.isalpha()All characters are letters"a1".isalpha()False
x in sIs x somewhere inside s"ell" in "hello"True

.split() and .join() are inverses, and together they do more work than any other pair of string methods:

name_formatter.py
raw = "  jOHN   smith  "

cleaned = raw.strip()          # 'jOHN   smith'
parts = cleaned.split()        # ['jOHN', 'smith']   <- split() with no argument
                               #    splits on ANY run of whitespace
first = parts[0].title()       # 'John'
last = parts[1].title()        # 'Smith'

print(f"{last}, {first}")      # 'Smith, John'
print("-".join(parts))         # 'jOHN-smith' 
Smith, John jOHN-smith

A worked program

password_strength.py
# password_strength.py
password = "Sunshine2026"

long_enough = len(password) >= 8
has_digit = any(ch.isdigit() for ch in password)
has_upper = password != password.lower()
has_lower = password != password.upper()

score = long_enough + has_digit + has_upper + has_lower   # booleans add as 1 and 0

print(f"Password: {password}")
print(f"  8+ characters : {long_enough}")
print(f"  has a digit   : {has_digit}")
print(f"  has upper     : {has_upper}")
print(f"  has lower     : {has_lower}")
print(f"  score         : {score}/4")
Password: Sunshine2026 8+ characters : True has a digit : True has upper : True has lower : True score : 4/4
Booleans are numbers

True + True is 2. That is why long_enough + has_digit + ... counts how many conditions passed. It is a genuinely useful trick, not a party trick.

>_Python playground

A real Python interpreter running inside your browser. Nothing is installed, nothing is uploaded, nothing can break.

scratch.pypython not loaded
Values for input(), comma separated →
Output appears here. The first run takes a few seconds while Python loads.

Exercise set

Checked automatically the moment you submit. Work top to bottom — each one assumes the last. Your answers are saved in this browser.

All Warm-up Core Challenge Reset day

Day 3 — Conditional Logic & Control Flow

Programs stop running straight through and start making decisions.

Continue →