Chapter 1: Python Foundations
Python is one of the most readable programming languages ever written. That is not an accident. Its creator, Guido van Rossum, designed it in the early 1990s with one strong opinion: code is read far more often than it is written, so readability should come first. The result is a language that looks almost like plain English, where the structure of a program is visible at a glance.
This chapter starts from zero. Pay attention to the small things — naming conventions, type rules, how operators behave. These basics appear in every program you will write from here on.
1.1 Your First Python Program
Open a terminal and type python3 to launch the interactive shell:
Python 3.12.0 (main, Oct 2 2023)
>>>
The >>> prompt means Python is waiting. Type the following and press Enter:
print("Hello, World!")
Hello, World!
print() displays whatever you put inside its parentheses. The quotes tell Python that Hello, World! is text, not a command.
For anything longer than a few lines, write your code in a .py file and run it with:
python3 my_script.py
Try It 1.1 — Print your name, then your city. Two separate
print()statements, two separate lines of output.
1.2 Variables
A variable is a name attached to a value stored in memory. When you write:
city = "Karachi"
Python stores "Karachi" in memory and labels it city. Wherever you write city in your code, Python substitutes "Karachi".
city = "Karachi"
temperature = 38
print(city)
print(temperature)
Karachi
38
A variable's value can change. Write a new assignment and the old value is replaced:
score = 10
print(score)
score = 25
print(score)
10
25
How Variable Assignment Works
┌─────────────────┐
city ───────▶│ "Karachi" │ (stored in memory)
└─────────────────┘
┌─────────────────┐
temperature ───▶│ 38 │ (stored in memory)
└─────────────────┘
The variable name on the left is the label. The value on the right is what gets stored. The = sign is the assignment operator — it points the label at the value.
Naming Rules
| Rule | Example |
|---|---|
| Lowercase letters, numbers, underscores only | record_count ✓ |
| Cannot start with a number | 1st_record ✗ |
| No spaces | record count ✗ |
| Case-sensitive | City ≠ city |
| Cannot be a Python keyword | for, if, while ✗ |
The Python convention is snake_case — all lowercase, underscores between words. So open_tickets, not OpenTickets or openTickets.
Names matter more than they seem:
# Hard to understand later
x = 500
y = 0.17
z = x * y
# Clear immediately
revenue = 500
tax_rate = 0.17
tax_amount = revenue * tax_rate
Both produce identical results. Only one is readable in six months.
Try It 1.2 — Create three variables: a person's name, their age, and their city. Print all three.
⚠ Common Mistake —
=is not==age = 25 # assigns 25 to ageage == 25 # checks whether age equals 25 (True/False)A single
=assigns. A double==compares. Python will either raise an error or silently do the wrong thing when you mix them up.
1.3 Data Types
Every value in Python has a type. The type controls what you can do with it — you can divide two numbers, but not two names.
The Four Core Types
| Type | Keyword | Example | Can do math? | Typical use |
|---|---|---|---|---|
| Integer | int | 42, -7, 0 | Yes | Counts, IDs, row numbers |
| Float | float | 3.14, -0.5 | Yes | Prices, rates, percentages |
| String | str | "hello", '42' | No (text operations) | Names, labels, file paths |
| Boolean | bool | True, False | No | Flags, conditions, comparisons |
1.3.1 Integers — int
Whole numbers, positive or negative.
rows_loaded = 9500
rows_expected = 10000
rows_failed = rows_expected - rows_loaded
print(rows_failed)
500
1.3.2 Floats — float
Numbers with a decimal point.
budget = 50000.00
spent = 32750.50
remaining = budget - spent
print(remaining)
17249.5
Note:
0.1 + 0.2in Python returns0.30000000000000004. Not a bug — it is how all computers store decimals in binary. For most work it is harmless.
1.3.3 Strings — str
Any sequence of characters. Single quotes, double quotes, or triple quotes for multi-line text.
name = "Sarah"
status = 'active'
message = """
Account: Sarah
Status: Active
Plan: Pro
"""
print(name)
print(status)
print(message)
Sarah
active
Account: Sarah
Status: Active
Plan: Pro
Joining two strings uses + — this is called concatenation:
first = "Data"
second = "Engineer"
role = first + " " + second
print(role)
Data Engineer
1.3.4 Booleans — bool
Holds exactly one of two values: True or False. Always comes from a comparison.
files_processed = 47
target = 50
target_met = files_processed >= target
print(target_met)
False
Use type() to check any value's type:
print(type(100))
print(type(3.14))
print(type("hello"))
print(type(True))
<class 'int'>
<class 'float'>
<class 'str'>
<class 'bool'>
Try It 1.3 — Create one variable of each type. Print each with its
type().
1.4 Type Conversion
Data rarely arrives in the right type. A CSV file gives you numbers as strings. User input is always a string, even if someone typed 42. Conversion fixes this.
count = "4500" # came in as text
print(type(count))
count = int(count) # converted to integer
print(type(count))
print(count + 500)
<class 'str'>
<class 'int'>
5000
Conversion Functions at a Glance
| Function | Converts to | Example | Result |
|---|---|---|---|
int() | Integer | int("250") | 250 |
float() | Float | float("3.5") | 3.5 |
str() | String | str(4096) | "4096" |
⚠ Common Mistake —
int()truncates, it does not roundprint(int(99.9)) # Returns 99, not 100print(int(4.7)) # Returns 4, not 5994Use
round()when you need rounding.int()simply drops everything after the decimal.
Conversion fails when the value makes no sense as the target type:
int("hello")
ValueError: invalid literal for int() with base 10: 'hello'
1.5 Operators
1.5.1 Arithmetic Operators
a = 17
b = 5
print(a + b) # 22
print(a - b) # 12
print(a * b) # 85
print(a / b) # 3.4 — always a float
print(a // b) # 3 — floor division, drops decimal
print(a % b) # 2 — modulo, the remainder
print(a ** 2) # 289 — exponentiation
22
12
85
3.4
3
2
289
Operator Reference
| Operator | Name | Example | Result |
|---|---|---|---|
+ | Addition | 10 + 3 | 13 |
- | Subtraction | 10 - 3 | 7 |
* | Multiplication | 10 * 3 | 30 |
/ | Division | 10 / 3 | 3.333... |
// | Floor division | 10 // 3 | 3 |
% | Modulo | 10 % 3 | 1 |
** | Exponentiation | 10 ** 3 | 1000 |
The modulo operator % deserves attention. 17 % 5 asks: after dividing 17 by 5, what is left over? The answer is 2. You will use this to check whether a number is even (n % 2 == 0), trigger logic every nth item, or split records into batches.
1.5.2 Comparison Operators
Comparisons always return a boolean:
score = 87
print(score == 100) # False — equal
print(score != 100) # True — not equal
print(score > 80) # True — greater than
print(score >= 87) # True — greater than or equal
print(score < 50) # False — less than
False
True
True
True
False
Try It 1.4 — A batch job loaded 9,847 out of 10,000 expected records. Calculate the success rate, then check whether it is above 98.5%.
1.6 Conditions: if, elif, else
So far every program runs the same lines in the same order. Real pipelines do not work that way. A nightly load finishes, and something has to decide: publish the data, raise a warning, or stop everything. That decision is a condition, and it is the point where a script starts behaving like an engineer.
1.6.1 The if Statement
An if statement runs a block of code only when its condition is True.
rows_loaded = 9847
rows_expected = 10000
if rows_loaded < rows_expected:
print("Warning: load is incomplete")
Warning: load is incomplete
Three details are not optional. The condition ends with a colon. The code that belongs to the if is indented (four spaces is the convention). Python uses that indentation, not braces, to know where the block ends. If the condition were False, the indented line would simply be skipped.
1.6.2 else
else handles everything the if did not match.
success_rate = 98.5
if success_rate >= 99:
print("Load healthy")
else:
print("Load needs review")
Load needs review
Exactly one of the two blocks runs, never both, never neither.
1.6.3 elif
When there are more than two outcomes, chain them with elif (short for "else if"). Python checks the conditions from top to bottom and runs the first one that is True, then skips the rest.
success_rate = 96.4
if success_rate == 100:
print("Perfect load")
elif success_rate >= 98:
print("Healthy load")
elif success_rate >= 95:
print("Needs review")
else:
print("Failed load, stop the pipeline")
Needs review
Because the first match wins, order matters. Put the most specific condition first:
success_rate = 100
if success_rate >= 95:
print("Needs review")
elif success_rate == 100:
print("Perfect load") # can never run
Needs review
A perfect load is also >= 95, so the first branch catches it and the elif is never reached. Nothing errors, the output is just wrong, which is exactly why this bug survives code review.
1.6.4 Combining Conditions
Real decisions rarely depend on one fact. Three keywords combine conditions:
| Keyword | Meaning | Example | Result |
|---|---|---|---|
and | Both sides must be true | True and False | False |
or | At least one side must be true | True or False | True |
not | Flips the result | not True | False |
rows_loaded = 9847
errors = 0
if rows_loaded >= 9500 and errors == 0:
print("Safe to publish")
Safe to publish
status = "timeout"
if status == "failed" or status == "timeout":
print("Retry the job")
Retry the job
When you are checking one value against several options, the in operator is shorter and easier to read than a chain of or:
status = "timeout"
if status in ["failed", "timeout", "cancelled"]:
print("Retry the job")
Retry the job
1.6.5 Truthiness
A condition does not have to be a comparison. Python treats some values as False on their own: 0, 0.0, an empty string "", and an empty list []. Everything else counts as True. That gives you a compact way to ask "is there anything here?"
failed_files = []
if failed_files:
print("Some files failed")
else:
print("No failures")
No failures
Three mistakes to expect. Writing
=where you meant==(one assigns, the other compares, and Python refuses the assignment inside anif). Forgetting the colon at the end of the line. Indenting inconsistently, which Python reports as anIndentationError.
Try It 1.5 — A pipeline reports its status as a string:
"success","warning", or"failed". Write anif/elif/elsethat prints "Publish", "Publish with a note", or "Stop and alert" for each one. Change the status value and confirm every branch runs.
1.7 Comments
Python ignores any line that starts with #. Comments are for humans — whoever reads the code next, including you six months from now.
# Count of files successfully processed today
files_done = 142
total_files = 150
success_rate = (files_done / total_files) * 100 # expressed as percentage
print(success_rate)
94.66666666666667
The general rule: comment on why, not what. The code already says what it does. Comments explain the reason behind a decision.
# Subtract 1 because the downstream system uses zero-based partition indexing
partition_id = (batch_number - 1)
1.8 Lists
A list holds multiple values in a single variable, in order. You create one with square brackets:
cities = ["Karachi", "Lahore", "Islamabad", "Peshawar"]
scores = [88, 92, 74, 95, 61]
Accessing Items
Every item in a list has an index — its position number, starting from 0.
Index: 0 1 2 3
┌──────────┬──────────┬────────────┬──────────┐
│ "Karachi"│ "Lahore" │"Islamabad" │"Peshawar"│
└──────────┴──────────┴────────────┴──────────┘
cities = ["Karachi", "Lahore", "Islamabad", "Peshawar"]
print(cities[0]) # First item
print(cities[2]) # Third item
print(cities[-1]) # Last item (negative index counts from end)
Karachi
Islamabad
Peshawar
Adding Items — append()
append() adds a new item to the end of the list:
cities = ["Karachi", "Lahore"]
cities.append("Quetta")
print(cities)
['Karachi', 'Lahore', 'Quetta']
Removing Items — pop()
pop() removes and returns the last item. Pass an index to remove a specific item:
cities = ["Karachi", "Lahore", "Islamabad"]
last = cities.pop() # removes and returns "Islamabad"
print(last)
print(cities)
removed = cities.pop(0) # removes and returns "Karachi"
print(removed)
print(cities)
Islamabad
['Karachi', 'Lahore']
Karachi
['Lahore']
List Length
len() returns the number of items in a list:
scores = [88, 92, 74, 95, 61]
print(len(scores))
5
Try It 1.6 — Create a list of five country names. Print the first and last items. Append a new country. Pop the second item and print what was removed. Print the final list.
1.9 Tuples
A tuple looks like a list but uses parentheses instead of square brackets — and it cannot be changed after it is created.
coordinates = (24.8607, 67.0011) # latitude, longitude
rgb = (255, 128, 0) # a colour
List vs Tuple — When to Use Which
| List | Tuple | |
|---|---|---|
| Syntax | [1, 2, 3] | (1, 2, 3) |
| Can change? | Yes — add, remove, edit | No — fixed once created |
| Use when | Data changes over time | Data is fixed (coordinates, config, records) |
| Speed | Slightly slower | Slightly faster |
# A list — can grow and shrink
pipeline_steps = ["extract", "transform", "load"]
pipeline_steps.append("validate")
print(pipeline_steps)
# A tuple — fixed, cannot be modified
db_config = ("localhost", 5432, "mydb")
print(db_config[0]) # access by index, same as list
print(db_config[1])
['extract', 'transform', 'load', 'validate']
localhost
5432
Trying to modify a tuple raises an error:
db_config = ("localhost", 5432, "mydb")
db_config[1] = 3306
TypeError: 'tuple' object does not support item assignment
This is intentional. Use a tuple when the data should never change — coordinates, settings, database credentials, column names. Use a list when the data will grow or be modified.
Try It 1.7 — Create a tuple with three server configuration values (host, port, database name). Print each value using its index. Try changing one value and observe the error.
1.10 Putting It Together
Here is a small program that uses everything from this chapter. Ayan is tracking project data for a client review:
# Client project summary
client = "Danial"
project = "Cloud Infrastructure Upgrade"
budget = 75000.00
tasks = ["planning", "setup", "migration", "testing"]
tasks_done = 2
completion = (tasks_done / len(tasks)) * 100
on_budget = budget <= 80000.00
tasks.append("handover") # new phase added
if completion >= 75:
status = "ahead of plan"
elif completion >= 50:
status = "on track"
else:
status = "behind plan"
print("Client: " + client)
print("Project: " + project)
print("Progress: " + str(round(completion, 1)) + "%")
print("Status: " + status)
print("On budget: " + str(on_budget))
print("Phases: " + str(tasks))
print("Total phases: " + str(len(tasks)))
Client: Danial
Project: Cloud Infrastructure Upgrade
Progress: 50.0%
Status: on track
On budget: True
Phases: ['planning', 'setup', 'migration', 'testing', 'handover']
Total phases: 5
Every concept from this chapter appears here: variables, strings, a float, a boolean, arithmetic, an if/elif/else decision, a list, append(), len(), and type conversion for the print statements.
Summary
Python stores values in variables, and every value has a type — int, float, str, or bool. The type controls what operations are valid. When data arrives in the wrong type, conversion functions (int(), float(), str()) fix it — but int() truncates rather than rounds. Arithmetic operators handle math; comparison operators return booleans. Conditions (if, elif, else, combined with and, or, not) let a program choose what to run, and the first matching branch wins. Comments explain why, not what. A list holds ordered values that can grow and change — use append() to add and pop() to remove. A tuple holds ordered values that are fixed — use one when the data should never change.
Exercises
1.1 — Create four variables: a string, an integer, a float, and a boolean. Print each with its type(). Then convert the integer to a float and the float to a string and print both results.
1.2 — You have a storage budget of 500 GB. A pipeline writes 37 GB per day. Calculate how many full days until the storage runs out, and how many GB remain on the last day. Use only // and %.
1.3 — The following code has a bug. Find it and explain exactly what Python will do when it runs:
limit = 1000
if limit = 1000:
print("Limit reached")
1.4 — Create a list of five scores. Print the highest (use max()), lowest (use min()), and the total (use sum()). Then append one more score and print the new average.
1.5 — What is the difference between [10, 20, 30] and (10, 20, 30)? Write a short program that demonstrates one operation that works on a list but fails on a tuple. Explain why the distinction matters.
1.6 — A rate comes in as the string "98.6". Write the steps to convert it to an integer 98 without losing the intermediate float. What would happen if you called int("98.6") directly, and why?
1.7 — A load reports rows_loaded, rows_expected, and errors. Write a program that prints "Publish" when at least 99% of expected rows loaded and there are no errors, "Review" when the load is between 95% and 99% or there are errors, and "Stop" when fewer than 95% loaded. Test it with three different sets of values, including one that shows why the order of your branches matters.