Skip to main content

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​

RuleExample
Lowercase letters, numbers, underscores onlyrecord_count ✓
Cannot start with a number1st_record ✗
No spacesrecord count ✗
Case-sensitiveCity ≠ city
Cannot be a Python keywordfor, 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 age
age == 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​

TypeKeywordExampleCan do math?Typical use
Integerint42, -7, 0YesCounts, IDs, row numbers
Floatfloat3.14, -0.5YesPrices, rates, percentages
Stringstr"hello", '42'No (text operations)Names, labels, file paths
BooleanboolTrue, FalseNoFlags, 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.2 in Python returns 0.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​

FunctionConverts toExampleResult
int()Integerint("250")250
float()Floatfloat("3.5")3.5
str()Stringstr(4096)"4096"

⚠ Common Mistake — int() truncates, it does not round

print(int(99.9)) # Returns 99, not 100
print(int(4.7)) # Returns 4, not 5
99
4

Use 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​

OperatorNameExampleResult
+Addition10 + 313
-Subtraction10 - 37
*Multiplication10 * 330
/Division10 / 33.333...
//Floor division10 // 33
%Modulo10 % 31
**Exponentiation10 ** 31000

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:

KeywordMeaningExampleResult
andBoth sides must be trueTrue and FalseFalse
orAt least one side must be trueTrue or FalseTrue
notFlips the resultnot TrueFalse
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 an if). Forgetting the colon at the end of the line. Indenting inconsistently, which Python reports as an IndentationError.

Try It 1.5 — A pipeline reports its status as a string: "success", "warning", or "failed". Write an if/elif/else that 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​

ListTuple
Syntax[1, 2, 3](1, 2, 3)
Can change?Yes — add, remove, editNo — fixed once created
Use whenData changes over timeData is fixed (coordinates, config, records)
SpeedSlightly slowerSlightly 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.