In the previous lesson you got the data of an Alba Studio task to live in variables. The program already stores information, but it still does not do anything with it: it cannot say how many days are left until the Solé Bakery logo is due, nor whether that task is urgent, nor what percentage of the job Luis has completed.

That leap — from storing to calculating — is made by operators. An operator is a symbol that combines values to produce a new one: + adds, > compares, and joins conditions. And the combination of values, variables and operators is called an expression. Every program, however complex, is deep down an accumulation of expressions that get evaluated and values that get assigned.

In this lesson you will learn what an expression is and in what order it is evaluated; the arithmetic operators, with special attention to the two that are usually ignored and turn out to be extremely useful (// and %); compound assignment (+= and company); the comparison operators, which always produce a boolean; the logical operators and, or and not with their truth tables and their lazy evaluation; the membership operator in; the difference between == and is; and how + and * work with text. We will close with precedence and with the application of all of it to EasyTask.

Contents

  1. What an expression is and how it is evaluated
  2. Arithmetic operators
  3. Floor division // and modulo %: the two underrated ones
  4. Compound assignment
  5. Comparison operators
  6. Logical operators and truth tables
  7. Lazy evaluation (short-circuiting)
  8. The membership operator in
  9. == versus is
  10. Operators with strings: + and *
  11. Operator precedence
  12. EasyTask: calculating on the task data
  13. Common mistakes and tips
  14. Exercises
  15. Conclusion

  1. What an expression is and how it is evaluated

An expression is any combination of values, variables and operators that Python can evaluate until it is reduced to a single value.

These are all expressions:

7
7 + 3
estimated_days * 8
hours_spent > 5
priority == "high"

And this one is not: days = 3. It is a statement (an assignment), not an expression. The distinction is subtle but clarifying:

Expression Statement
What it does It reduces to a value It performs an action
Example 7 + 310 total = 7 + 3
Can it go on the right of an =? Yes No

Notice that an assignment contains an expression: in total = 7 + 3, the right-hand side is the expression and the = is the action of storing its result.

Evaluating means replacing each piece with its value until only one is left. With this code:

estimated_days = 3
hours_per_day = 8

total_hours = estimated_days * hours_per_day
print(total_hours)        # 24

Python does the following when it reaches the third line:

estimated_days * hours_per_day
       3        *       8
                24

First it replaces each name with the value it points at, then it applies the operator, and finally it assigns the result. That order — values first, operation next, assignment last — is the one you should have in your head when reading any line of code.

The values an operator acts on are called operands. In 3 * 8, the operator is * and the operands are 3 and 8.

  1. Arithmetic operators

Python offers seven arithmetic operators. The first three will not surprise you; the next four deserve attention.

Operator Name Example Result Type of the result
+ Addition 7 + 3 10 int
- Subtraction 7 - 3 4 int
* Multiplication 7 * 3 21 int
/ True division 7 / 3 2.3333333333333335 float always
// Floor division 7 // 3 2 int (if both are int)
% Modulo (remainder) 7 % 3 1 int
** Power 7 ** 3 343 int

Let's see them at work:

print(7 + 3)      # 10
print(7 - 3)      # 4
print(7 * 3)      # 21
print(7 / 3)      # 2.3333333333333335
print(7 // 3)     # 2
print(7 % 3)      # 1
print(7 ** 3)     # 343

There is a classic trap in the fourth line. / always returns a float, even when the division is exact:

print(6 / 3)          # 2.0   <- not 2
print(type(6 / 3))    # <class 'float'>
print(6 // 3)         # 2
print(type(6 // 3))   # <class 'int'>

If you need a whole number — to count things, for instance — use //, not /. A task counter worth 2.0 instead of 2 produces ugly output and confusing comparisons.

Another detail: if you mix an int and a float in any operation, the result is a float. This is implicit conversion, and we will study it in more detail in Type Conversion and Validation:

print(3 + 1.5)          # 4.5
print(type(3 + 1.5))    # <class 'float'>

Negative signs work as you expect, and - can also be used with a single operand (it is called unary minus):

difference = 3 - 5
print(difference)     # -2
print(-difference)    # 2

A warning about / and //: dividing by zero is an error. 7 / 0 stops the program with ZeroDivisionError: division by zero. When the divisor comes from a variable piece of data (for example, the number of tasks a person has, which may be zero), you will have to check it first. Making that check requires conditionals, which arrive in Conditionals.

  1. Floor division // and modulo %: the two underrated ones

These two operators are usually skimmed over and then turn out to be the ones most used in real problems. They deserve a section of their own.

Think of the division you learned at school: 17 divided by 5 gives 3 as the quotient and 2 as the remainder. Well then:

  • // gives you the whole quotient: 17 // 53
  • % gives you the remainder: 17 % 52
total_minutes = 195

hours = total_minutes // 60      # 3
minutes = total_minutes % 60     # 15

print(hours)      # 3
print(minutes)    # 15

195 minutes are 3 hours and 15 minutes. This pair of operators is the standard way of breaking a quantity down into larger and smaller units: minutes into hours, days into weeks, cents into pounds.

Applied to Alba Studio: Luis has spent 27 hours on a job and works 8-hour days. How many full working days has he put in and how many odd hours of the next one?

HOURS_PER_WORKDAY = 8
hours_spent = 27

full_workdays = hours_spent // HOURS_PER_WORKDAY    # 3
spare_hours = hours_spent % HOURS_PER_WORKDAY       # 3

print(full_workdays)    # 3
print(spare_hours)      # 3

The other great use of % is detecting multiples. A number is a multiple of another when the remainder of dividing them is zero:

task_number = 12

print(task_number % 2)      # 0  -> it is even
print(task_number % 5)      # 2  -> not a multiple of 5

n % 2 == 0 means "n is even". It is a construction you will see a thousand times.

Need Operator Example
Split into complete groups // 27 // 8 → 3 whole working days
Know what is left over from the split % 27 % 8 → 3 odd hours
Check whether it is a multiple / even % 12 % 2 → 0, so it is even
Get the last digit % 1250 % 10 → 0
Exact value with decimals / 27 / 8 → 3.375 working days

A warning about negatives, so that it does not catch you by surprise: -7 // 2 gives -4, not -3, because Python rounds downwards, not towards zero. And -7 % 2 gives 1, not -1. With positive quantities — 99% of cases — you will not notice the difference.

  1. Compound assignment

Increasing the value of a variable is so frequent that Python offers a shorthand. These two lines do exactly the same thing:

hours_spent = hours_spent + 2
hours_spent += 2

The second reads as "add 2 to hours_spent". There is a shorthand version for every arithmetic operator:

Shorthand Equivalent to Example (starting from x = 10) Result
x += 3 x = x + 3 13
x -= 3 x = x - 3 7
x *= 3 x = x * 3 30
x /= 4 x = x / 4 2.5 (float)
x //= 3 x = x // 3 3
x %= 3 x = x % 3 1
x **= 2 x = x ** 2 100

An example following the thread of the course: Luis notes down the hours he spends on the logo over the week.

hours_spent = 0

hours_spent += 3.5      # Monday
hours_spent += 2        # Tuesday
hours_spent += 4        # Thursday

print(hours_spent)      # 9.5

Notice an essential detail: the variable must exist beforehand. hours_spent += 3.5 is hours_spent = hours_spent + 3.5, and to calculate the right-hand side Python needs to know what it is worth now. If it has never been assigned, you will get a NameError. That is why the first line initialises it to 0.

It also works with text, because + concatenates strings (we will see it in section 10):

summary = "Task: "
summary += "logo"
print(summary)              # Task: logo

  1. Comparison operators

Comparison operators answer questions about two values. And they do it in a very specific way: they always return a boolean value, True or False. Never anything else.

Operator Meaning Example Result
== Are they equal? 3 == 3 True
!= Are they different? 3 != 5 True
> Greater than? 3 > 5 False
< Less than? 3 < 5 True
>= Greater than or equal? 5 >= 5 True
<= Less than or equal? 3 <= 2 False
estimated_days = 3
elapsed_days = 5

print(elapsed_days > estimated_days)      # True  -> it is running late
print(estimated_days == elapsed_days)     # False
print(estimated_days != elapsed_days)     # True

That True you see printed is not decorative text: it is a value of type bool, and as such it can be stored in a variable and used later on.

is_late = elapsed_days > estimated_days
print(is_late)              # True
print(type(is_late))        # <class 'bool'>

This is a powerful idea and it is worth underlining: a comparison produces a piece of data like any other. It is not a question that gets asked and forgotten; it is a value you can store, name and reuse. When the conditionals arrive in module 3, that value will be the one deciding which path the program takes.

Comparisons also work with text, and there are two things to know there:

print("high" == "high")     # True
print("high" == "HIGH")     # False  <- case sensitive
print("high" < "medium")    # True   <- alphabetical order

The second line is exactly the problem we discovered with the desk check in lesson 01-05: "HIGH" and "high" are different pieces of text for Python. The solution arrives in Type Conversion and Validation.

The third shows that < and > compare text alphabetically, character by character according to its internal code. Careful: since upper-case letters come before lower-case ones in that order, "Vidal" < "alba" is True. It is a source of surprises when sorting names.

Python also lets you chain comparisons, something unusual in other languages and very readable:

days = 5
print(1 <= days <= 7)       # True  -> it is within the week

That reads exactly as it looks: "days is between 1 and 7, both included".

  1. Logical operators and truth tables

Logical operators combine boolean values to build compound conditions. There are three of them:

Operator Meaning Returns True when...
and And Both operands are true
or Or At least one is true
not Not It inverts the value of the operand

In Python they are written as English words, not with symbols like && or || from other languages.

Truth table for and:

A B A and B
True True True
True False False
False True False
False False False

Truth table for or:

A B A or B
True True True
True False True
False True True
False False False

Truth table for not:

A not A
True False
False True

And now with Alba Studio data:

priority = "high"
completed = False

is_urgent = priority == "high" and not completed
print(is_urgent)            # True

Let's take that expression apart, because it condenses almost everything seen so far:

priority == "high"  and  not completed
      True          and  not False
      True          and     True
                 True
  1. priority == "high" is a comparison → True.
  2. completed is False, so not completedTrue.
  3. True and TrueTrue.

It reads almost literally as plain English: "it is urgent if the priority is high and it is not completed". When a logical expression reads well out loud, it is usually well written.

Now the other way round, with the task already finished:

priority = "high"
completed = True

is_urgent = priority == "high" and not completed
print(is_urgent)            # False

not True is False, and True and False is False. The task is high priority but it is already done: it is not urgent. Correct.

An example with or, to decide whether a task needs a review from Marta:

priority = "medium"
hours_spent = 22
HOURS_LIMIT = 20

needs_review = priority == "high" or hours_spent > HOURS_LIMIT
print(needs_review)         # True

It is not high priority (False), but it has gone over the hours limit (True). With or it is enough for one of them to hold: True.

Be careful with an extremely frequent mistake when translating from everyday speech. This does not work the way it looks:

priority = "low"

# INCORRECT
is_relevant = priority == "high" or "medium"
print(is_relevant)          # medium  <- not even a boolean

In English we say "the priority is high or medium", but Python does not read it that way: it evaluates priority == "high" (which gives False) and then evaluates "medium" on its own, as an independent value. A non-empty piece of text counts as true, so the or returns "medium" and the expression turns out to be true always, even with a low priority. The correct form repeats the full comparison:

# CORRECT
is_relevant = priority == "high" or priority == "medium"
print(is_relevant)          # False

Or, better still, use the in operator we will see in section 8.

  1. Lazy evaluation (short-circuiting)

Python evaluates logical expressions from left to right and stops as soon as it knows the result. This is what is called lazy evaluation or short-circuiting.

The logic is simple:

  • In A and B: if A is already False, the result will be False whatever happens with B. Python does not even evaluate B.
  • In A or B: if A is already True, the result will be True. Python does not evaluate B.
flowchart TD
    A["Evaluate A and B"] --> B{"Is A False?"}
    B -->|Yes| C["Result False<br/>B is not evaluated"]
    B -->|No| D["Evaluate B"]
    D --> E["The result is the value of B"]

Why does this matter? For two very practical reasons.

First, for efficiency. If the right-hand side is an expensive calculation, saving it is worth something.

Second, and far more importantly, for safety. Short-circuiting lets you protect a dangerous operation by putting the check that makes it safe in front of it:

assigned_tasks = 0
total_hours = 0

# If assigned_tasks is 0, the division never runs
workload_ok = assigned_tasks > 0 and total_hours / assigned_tasks < 10
print(workload_ok)     # False, with no ZeroDivisionError

If Python always evaluated both sides, the division by zero would break the program. Thanks to short-circuiting, since assigned_tasks > 0 is False, the right-hand side is not even looked at. The order of the operands in an and is not a matter of indifference: first the check that protects, then the protected operation.

A curious nuance worth knowing: in Python, and and or do not always return True or False, but the operand they stopped at.

print(0 or "unassigned")        # unassigned
print("Luis" or "unassigned")   # Luis

Since 0 counts as false, or carries on and returns the second operand. Since "Luis" counts as true, it stops and returns it. It is a common idiom for supplying default values. The exact rules of which values count as true you will see in Type Conversion and Validation.

  1. The membership operator in

The in operator answers the question "is this inside that?" and returns a boolean. On text, it checks whether one string appears inside another:

title = "Sole Bakery logo"

print("Sole" in title)           # True
print("Vidal" in title)          # False
print("sole" in title)           # False  <- case sensitive

Its negated form is not in:

print("Vidal" not in title)      # True

in works on lists, dictionaries and other structures you will see in module 5. But there is a use you can take advantage of from today and which elegantly solves the problem from section 6: checking whether a value is among several possible ones.

priority = "medium"

# Long version
is_valid = priority == "high" or priority == "medium" or priority == "low"

# Short and clear version
is_valid = priority in ("high", "medium", "low")

print(is_valid)       # True

The parentheses with values separated by commas form a tuple, a fixed collection of values studied in Tuples and Nested Structures. Here we use it only as a list of valid options: it is readable, it is extended by adding a value and it avoids repeating the variable's name three times. It will be our main tool for validating the priority in lesson 02-04.

A warning about in on text: it checks substrings, not whole words. "high" in "very high priority" is True, but so is "high" in "highlight the logo". For specific values, prefer in with a tuple of options over in on a long piece of text.

  1. == versus is

These two operators look like synonyms and they are not. The distinction is easy to understand with the labels-and-values model from the previous lesson:

  • == asks: are they worth the same?
  • is asks: are they exactly the same object in memory?
assignee_a = "Luis"
assignee_b = "Luis"

print(assignee_a == assignee_b)     # True  -> same content
print(assignee_a is assignee_b)     # True or False, depending on internal optimisations

That second line is precisely the problem: the result of is with text and numbers depends on internal details of Python that you do not control and that may change between versions. With short strings Python reuses the same object and gives True; with strings built at run time it may give False even though the content is identical.

The practical rule is simple and admits no exceptions at the level of this course:

You want to know... Use Never use
Whether two pieces of data have the same value == is
Whether a variable is None is None == None

That is: always use == to compare values, and reserve is for a single case, checking for None:

due_date = None

print(due_date is None)         # True
print(due_date is not None)     # False

Why is there? Because None is a unique value throughout the whole execution of Python: only one exists. Asking whether something "is that object" is exactly what you want, and it is the form you will see in all professional code.

  1. Operators with strings: + and *

Two arithmetic operators take on a second meaning when working with text.

+ concatenates, that is, it glues two strings into one:

business_name = "Sole"
business_type = "Bakery"

client = business_name + " " + business_type
print(client)       # Sole Bakery

Notice the " " in the middle: concatenation does not add spaces. Without it you would get SoleBakery. It is the most common oversight when concatenating.

* repeats a string a whole number of times:

separator = "-" * 40
print(separator)    # ----------------------------------------

print("=" * 10)     # ==========

It is the usual way of drawing separator lines without typing forty dashes by hand. We will use it in the EasyTask card.

And now the important limit. You cannot add text and a number:

days = 3
print("Days: " + days)
TypeError: can only concatenate str (not "int") to str

The message says it literally: "you can only concatenate str with str, not with int". Python does not guess what you mean. And rightly so: "3" + 4 could be 7 or "34", and ambiguity in a programming language is worse than an error.

The solution goes through converting the number to text with str(), and that is covered in Type Conversion and Validation. In practice, to mix text and data you will use the f-strings from Input and Output, which are far more comfortable. That is why in this lesson we keep printing each value in its own print.

Operator With numbers With text
+ Adds: 3 + 47 Concatenates: "a" + "b""ab"
* Multiplies: 3 * 412 Repeats: "ab" * 3"ababab"
- Subtracts: 7 - 34 Error: TypeError
/ Divides Error: TypeError

  1. Operator precedence

When an expression combines several operators, Python applies a fixed order called precedence, just as in mathematics multiplication comes before addition.

print(2 + 3 * 4)      # 14, not 20

From highest to lowest priority:

Level Operators Example
1 (highest) () parentheses (2 + 3) * 420
2 ** power 2 ** 3 * 432
3 -x unary minus -2 ** 2-4
4 *, /, //, % 2 + 3 * 414
5 +, -
6 ==, !=, <, >, <=, >=, in, is 2 + 3 > 4True
7 not
8 and
9 (lowest) or

Two consequences worth remembering:

Comparisons are evaluated after the arithmetic. That is why 2 + 3 > 4 works: first it adds (5), then it compares (5 > 4True). No parentheses needed.

and is evaluated before or. This one does come as a surprise:

print(True or False and False)     # True

It is grouped as True or (False and False)True or FalseTrue. If you had wanted (True or False) and False, the result would be False. Here parentheses are not optional: they are the difference between two different programs.

And the practical advice, which is worth more than the whole table: use parentheses whenever the expression is not obvious. They cost nothing, they do not slow the program down and they save whoever reads the code — or you yourself a month from now — from having to consult a precedence table.

# Correct but it requires knowing the table
is_critical = priority == "high" and not completed or hours_spent > 40

# Correct and obvious
is_critical = (priority == "high" and not completed) or (hours_spent > 40)

The two lines do the same thing. The second is understood without thinking.

  1. EasyTask: calculating on the task data

We apply everything learned to easytask.py. We start from the variables of version 0.2 and add calculations: the days left until delivery, whether the task is urgent and the progress percentage of the Solé Bakery job.

# easytask.py - Alba Studio
# Version 0.3: calculations on the task data

APP_NAME = "EasyTask"
VERSION = "0.3"
HOURS_PER_WORKDAY = 8

# --- Task data ---
title = "Sole Bakery logo"
assignee = "Luis"
priority = "high"
completed = False

current_day = 12              # day of the month we are on
due_day = 19                  # day of the month agreed with the client
estimated_hours = 24          # hours budgeted for the job
hours_spent = 9               # hours Luis has put in so far

print("=" * 40)
print("  EASYTASK v0.3 - Alba Studio")
print("=" * 40)
print(title)
print(assignee)
print("-" * 40)

# --- 1. Days left until delivery ---
remaining_days = due_day - current_day
print("Remaining days:")
print(remaining_days)                       # 7

# --- 2. Working days and odd hours spent ---
workdays = hours_spent // HOURS_PER_WORKDAY
spare_hours = hours_spent % HOURS_PER_WORKDAY
print("Full working days spent:")
print(workdays)                             # 1
print("Odd hours:")
print(spare_hours)                          # 1

# --- 3. Progress percentage ---
progress_percentage = hours_spent / estimated_hours * 100
print("Progress percentage:")
print(progress_percentage)                  # 37.5

# --- 4. Hours left and pace required ---
remaining_hours = estimated_hours - hours_spent
print("Remaining hours:")
print(remaining_hours)                      # 15

# --- 5. Is it urgent? ---
is_urgent = priority == "high" and not completed
print("Is urgent:")
print(is_urgent)                            # True

# --- 6. Valid priority? ---
valid_priority = priority in ("high", "medium", "low")
print("Valid priority:")
print(valid_priority)                       # True

# --- 7. Is it running late? ---
# It is considered tight if it has used more than 60 % of the hours
# budget and fewer than 5 days are left
tight_on_time = (progress_percentage > 60) and (remaining_days < 5)
print("Tight on time:")
print(tight_on_time)                        # False

Let's comment on the calculations that have some substance.

Remaining days (due_day - current_day). A simple subtraction that only works within the same month; for real dates there is Python's datetime module, which is outside the scope of this introductory course. What matters here is that the data is no longer written down: it is calculated. If you change current_day to 17, remaining_days becomes 2 without touching anything else.

Progress percentage (hours_spent / estimated_hours * 100). Note that here we use / and not //: we want the exact value with decimals. With 9 hours out of 24, the result is 37.5. And observe the precedence: / and * have the same level, so they are evaluated from left to right: first 9 / 24 (= 0.375) and then * 100 (= 37.5). Which is what we wanted.

Working days and odd hours (// and %). 9 hours with 8-hour days are 1 full working day and 1 odd hour. It is the pattern from section 3 applied to real data.

Is urgent (priority == "high" and not completed). The expression that sums up Marta's business rule. It is high and it is not done: urgent. Try setting completed = True and running again: it turns to False.

Tight on time. Here the parentheses were not needed for precedence (comparisons come before and), but they have been put in so that the line reads at a glance. That is exactly the criterion you should apply.

Desk check of the urgency expression

As in module 1, let's verify the logic with a table, without running anything:

Case priority completed priority == "high" not completed is_urgent
1 "high" False True True True
2 "high" True True False False
3 "medium" False False True False
4 "medium" True False False False
5 "HIGH" False False True False ← problem

The first four cases are correct. The fifth puts its finger on the sore spot again: if Marta types the priority in upper case, the comparison fails and an urgent task goes unnoticed. It is not a flaw in the logical expression, but in the data reaching it. That is the job of validation, and we will solve it in 02-04.

Common Mistakes and Tips

Using = instead of == when comparing. priority = "high" assigns; priority == "high" compares. In modern Python, putting = where == belongs inside a condition gives a SyntaxError, which is a piece of luck: the error shows up immediately.

Expecting an integer from /. 10 / 2 is 5.0, a float. If you are counting things, use //.

Dividing by zero. ZeroDivisionError. When the divisor is a variable piece of data, protect it with an and taking advantage of short-circuiting, or with a conditional once you reach module 3.

Writing and, or and not as symbols. In Python there is no &&, no || and no !. They are words.

The x == "high" or "medium" trap. It looks like plain English and it is not. Repeat the whole comparison or, better, use x in ("high", "medium").

Using is to compare values. It works by accident with short strings and fails just when the program is already in use. == for values; is only with None.

Concatenating text and a number with +. "Days: " + 3 gives a TypeError. Wait for the f-strings of the next lesson: that is the comfortable solution.

Forgetting the space when concatenating. "Sole" + "Bakery" produces SoleBakery. The space has to be put in explicitly.

Trusting precedence from memory. Nobody remembers whether not comes before and. Put in parentheses and stop thinking about it.

Trusting exact comparison of float values. 0.1 + 0.2 == 0.3 is False, because decimals are stored approximately. With money or measurements, compare with a tolerance margin instead of demanding exact equality.

Tip: name your logical expressions. Instead of repeating priority == "high" and not completed in several places, store it in is_urgent and use the name. The code reads like a sentence and, if the rule changes, you touch a single place.

Tip: use the REPL as a calculator. When you are unsure what -7 // 2 gives, or "high" < "medium", do not reason it out: open the interpreter and try it. It takes five seconds and settles the doubt forever.

Exercises

Exercise 1: Evaluating expressions by hand

Write down the result and the type of each expression without running anything. Then check it in the REPL.

a)  17 // 5
b)  17 % 5
c)  17 / 5
d)  2 ** 5
e)  10 - 4 * 2
f)  (10 - 4) * 2
g)  7 // 2 == 3
h)  "high" == "High"
i)  not (3 > 5)
j)  True and False or True
k)  "prio" in "priority"
l)  "-" * 5
m)  1 <= 4 <= 3

Exercise 2: Splitting hours at Alba Studio

Marta is splitting the work of a big job. Write a program that, starting from these variables, calculates and displays what is asked for:

job_hours = 100
people = 3
HOURS_PER_WORKDAY = 8
hours_done = 37

Calculate:

  1. How many whole hours each person gets (no decimals).
  2. How many hours are left over from the split and cannot be shared out equally.
  3. The exact split with decimals.
  4. The percentage of the job already done.
  5. How many full 8-hour working days are left to do.
  6. A boolean more_than_half indicating whether more than 50 % of the job has been passed.

Exercise 3: EasyTask business rules

Marta has settled on three new rules. Write them as boolean expressions stored in named variables, starting from these initial variables:

priority = "medium"
completed = False
assignee = "Nuria"
remaining_days = 2
hours_spent = 18
estimated_hours = 20

The rules:

  • needs_attention_today: the task is not completed and either it is high priority or fewer than 3 days are left until delivery.
  • over_budget: more than 85 % of the estimated hours have been used and the task is still not completed.
  • is_design_work: the assignee is Luis or Nuria (Marta coordinates, she does not design).

Then state what value each one has with the data above and add a desk check of needs_attention_today for three different scenarios.

Solutions

Solution 1.

Expression Result Type Explanation
a 17 // 5 3 int Whole quotient
b 17 % 5 2 int Remainder
c 17 / 5 3.4 float / always returns a decimal
d 2 ** 5 32 int 2 to the power of 5
e 10 - 4 * 2 2 int * before -: 10 - 8
f (10 - 4) * 2 12 int The parentheses change the order
g 7 // 2 == 3 True bool // before ==: 3 == 3
h "high" == "High" False bool Case sensitive
i not (3 > 5) True bool 3 > 5 is False; not False is True
j True and False or True True bool and first: (True and False) or TrueFalse or True
k "prio" in "priority" True bool It is a substring
l "-" * 5 "-----" str String repetition
m 1 <= 4 <= 3 False bool 1 <= 4 yes, but 4 <= 3 no

Solution 2.

job_hours = 100
people = 3
HOURS_PER_WORKDAY = 8
hours_done = 37

# 1. Whole hours per person
hours_per_person = job_hours // people
print(hours_per_person)             # 33

# 2. Hours left over from the split
leftover_hours = job_hours % people
print(leftover_hours)               # 1

# 3. Exact split
exact_split = job_hours / people
print(exact_split)                  # 33.333333333333336

# 4. Percentage done
percentage_done = hours_done / job_hours * 100
print(percentage_done)              # 37.0

# 5. Full working days left
pending_hours = job_hours - hours_done
pending_workdays = pending_hours // HOURS_PER_WORKDAY
print(pending_hours)                # 63
print(pending_workdays)             # 7

# 6. Past the halfway mark?
more_than_half = percentage_done > 50
print(more_than_half)               # False

Comments on the decisions taken:

  • In 1 and 2 we use // and % because the split has to be in whole hours: 33 hours each and 1 hour left over that will have to be given to somebody.
  • In 3 we use / because there the exact value is what matters. The endless decimals can be presented readably with the f-strings of the next lesson.
  • In 5 we use //: 63 pending hours give 7 full working days (and 7 hours would be left over, which you would get with 63 % 8).

Solution 3.

priority = "medium"
completed = False
assignee = "Nuria"
remaining_days = 2
hours_spent = 18
estimated_hours = 20

needs_attention_today = (not completed) and (priority == "high" or remaining_days < 3)
print(needs_attention_today)        # True

percentage_used = hours_spent / estimated_hours * 100
over_budget = percentage_used > 85 and not completed
print(over_budget)                  # True

is_design_work = assignee in ("Luis", "Nuria")
print(is_design_work)               # True

All three are True with the starting data:

  • needs_attention_today: it is not completed (True) and, although the priority is not high (False), 2 days are left, which is fewer than 3 (True). The or gives True and so does the and. The parentheses around the or are essential: without them, and would be evaluated first and the rule would mean something else.
  • over_budget: 18 out of 20 hours is 90 %, which exceeds 85 %, and it is not completed.
  • is_design_work: Nuria is in the tuple of designers.

Desk check of needs_attention_today:

Scenario completed priority remaining_days not completed priority == "high" remaining_days < 3 or Result
Nuria's task, short deadline False "medium" 2 True False True True True
Luis's task, high, with slack False "high" 10 True True False True True
Task already delivered, high True "high" 1 False True True True False

The third scenario is the one that validates the design of the rule: however high the priority and however close the deadline, a completed task does not need attention. The not completed on the left-hand side of the and cuts off any other consideration, and it does so by short-circuiting: being False, Python does not even evaluate the parenthesised or.

Conclusion

Your variables have stopped sitting still. You now know that an expression is any combination of values, variables and operators that Python reduces to a single value, and that it is evaluated by first replacing each name with its value and then applying the operators in order of precedence.

You handle the seven arithmetic operators, with the critical distinction between / (always float) and // (whole), and you know that % is not a mathematical curiosity but the standard tool for breaking quantities down and detecting multiples. You write increments with compound assignment (+=). You know that comparisons produce booleans you can store and name, that the logical operators and, or and not behave according to predictable truth tables, and that their lazy evaluation not only saves work but also lets you protect dangerous operations by placing the check on the left. You use in to ask about membership, you reserve is exclusively for None, you concatenate and repeat strings with + and *, and you put in parentheses whenever precedence is not obvious.

EasyTask is at version 0.3 and it already calculates: days left until delivery, working days invested, progress percentage of the Solé Bakery job and an urgency expression (priority == "high" and not completed) that turns a real business rule of Marta's into code.

And yet the program still has two obvious shortcomings. The first is presentational: each item needs its own print and the resulting card is illegible; we have not been able to mix text and values on a single line because "Days: " + 3 gives a TypeError. The second is more fundamental: all the data is still written inside the code. To register another task you have to open the editor.

Both are solved in the next lesson, Input and Output, where you will learn to give the output a professional format with f-strings and to ask the user for the data from the keyboard with input(). From then on, EasyTask will stop being a program that gets edited and become a program that gets used.

© Copyright 2026. All rights reserved