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
- What an expression is and how it is evaluated
- Arithmetic operators
- Floor division
//and modulo%: the two underrated ones - Compound assignment
- Comparison operators
- Logical operators and truth tables
- Lazy evaluation (short-circuiting)
- The membership operator
in ==versusis- Operators with strings:
+and* - Operator precedence
- EasyTask: calculating on the task data
- Common mistakes and tips
- Exercises
- Conclusion
- 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:
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 + 3 → 10 |
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) # 24Python does the following when it reaches the third line:
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.
- 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) # 343There 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:
Negative signs work as you expect, and - can also be used with a single operand (it is called unary minus):
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.
- Floor division
// and modulo %: the two underrated ones
// and modulo %: the two underrated onesThese 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 // 5→3%gives you the remainder:17 % 5→2
total_minutes = 195
hours = total_minutes // 60 # 3
minutes = total_minutes % 60 # 15
print(hours) # 3
print(minutes) # 15195 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) # 3The 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 5n % 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.
- Compound assignment
Increasing the value of a variable is so frequent that Python offers a shorthand. These two lines do exactly the same thing:
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.5Notice 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):
- 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) # TrueThat 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.
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 orderThe 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:
That reads exactly as it looks: "days is between 1 and 7, both included".
- 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) # TrueLet's take that expression apart, because it condenses almost everything seen so far:
priority == "high"is a comparison →True.completedisFalse, sonot completed→True.True and True→True.
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) # Falsenot 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) # TrueIt 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 booleanIn 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:
Or, better still, use the in operator we will see in section 8.
- 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: ifAis alreadyFalse, the result will beFalsewhatever happens withB. Python does not even evaluateB. - In
A or B: ifAis alreadyTrue, the result will beTrue. Python does not evaluateB.
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 ZeroDivisionErrorIf 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.
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.
- The membership operator
in
inThe 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 sensitiveIts negated form is not in:
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) # TrueThe 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.
== versus is
== versus isThese 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?isasks: 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 optimisationsThat 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:
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.
- Operators with strings:
+ and *
+ 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 BakeryNotice 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:
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 + 4 → 7 |
Concatenates: "a" + "b" → "ab" |
* |
Multiplies: 3 * 4 → 12 |
Repeats: "ab" * 3 → "ababab" |
- |
Subtracts: 7 - 3 → 4 |
Error: TypeError |
/ |
Divides | Error: TypeError |
- Operator precedence
When an expression combines several operators, Python applies a fixed order called precedence, just as in mathematics multiplication comes before addition.
From highest to lowest priority:
| Level | Operators | Example |
|---|---|---|
| 1 (highest) | () parentheses |
(2 + 3) * 4 → 20 |
| 2 | ** power |
2 ** 3 * 4 → 32 |
| 3 | -x unary minus |
-2 ** 2 → -4 |
| 4 | *, /, //, % |
2 + 3 * 4 → 14 |
| 5 | +, - |
|
| 6 | ==, !=, <, >, <=, >=, in, is |
2 + 3 > 4 → True |
| 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 > 4 → True). No parentheses needed.
and is evaluated before or. This one does come as a surprise:
It is grouped as True or (False and False) → True or False → True. 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.
- 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) # FalseLet'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 <= 3Exercise 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:
Calculate:
- How many whole hours each person gets (no decimals).
- How many hours are left over from the split and cannot be shared out equally.
- The exact split with decimals.
- The percentage of the job already done.
- How many full 8-hour working days are left to do.
- A boolean
more_than_halfindicating 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 = 20The 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 True → False 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) # FalseComments 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 with63 % 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) # TrueAll 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). TheorgivesTrueand so does theand. The parentheses around theorare essential: without them,andwould 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.
Fundamentals of Programming
Module 1: Introduction to Programming
- What is programming?
- History of programming
- Programming languages
- Development environments
- From problem to algorithm
Module 2: Core Concepts
- Variables and data types
- Operators and expressions
- Input and output
- Type conversion and data validation
Module 3: Control Structures
Module 4: Functions and Procedures
- Defining and using functions
- Parameters and return values
- Variable scope
- Breaking a program down into functions
- Functions as values: lambda and higher order
Module 5: Data Structures
- Lists and arrays
- Strings
- Dictionaries and sets
- Tuples and nested structures
- Saving data to files: text, CSV and JSON
Module 6: Basic Algorithms
Module 7: Objects and Code Organisation
- From data to objects: classes and instances
- Attributes, methods and the constructor
- Collections of objects
- Modules, packages and imports
Module 8: Good Practices and Tools
- Documentation and comments
- Debugging and error handling
- Version control
- Automated testing
- Style, readability and refactoring
