0% found this document useful (0 votes)
6 views54 pages

Python Comprehensive Guide

The Comprehensive Python Guide is a detailed resource covering Python programming from basic concepts to advanced topics, structured in 18 sections. It includes explanations of data types, control structures, object-oriented programming, and algorithms, making it suitable for all skill levels. The guide emphasizes practical examples and coding practices to enhance understanding and application of Python.

Uploaded by

entity9993
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views54 pages

Python Comprehensive Guide

The Comprehensive Python Guide is a detailed resource covering Python programming from basic concepts to advanced topics, structured in 18 sections. It includes explanations of data types, control structures, object-oriented programming, and algorithms, making it suitable for all skill levels. The guide emphasizes practical examples and coding practices to enhance understanding and application of Python.

Uploaded by

entity9993
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Comprehensive Python Guide Sections 1–18 · All Skill Levels

■ Python
Comprehensive
Programming Guide
From First Print Statement to Recursion & OOP
Sections 1 – 18 · CS Definitions · Code Examples · All Skill Levels
Covers: Basics · Data Types · Loops · Scope · Operations · Methods · Input
Conditionals · F-strings · Random · Algorithms · Files · Arrays
Lists/Tuples/Dicts · OOP · Big 4 · Sorting & Searching · Recursion

—1—
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Table of Contents
Section 1 · Basics
print, output, language fundamentals
Section 2 · Data Types & Type Casting
int, float, bool, str, None + casting
Section 3 · Loops
for, while, range, for-each, break/continue
Section 4 · Nested Structures & Scope
nesting, LEGB, global/nonlocal
Section 5 · Operations
arithmetic, math library, concatenation, shorthand
Section 6 · Methods & Parameters
functions, params, arguments, overloading
Section 7 · Input
input(), tokens, parsing, type conversion
Section 8 · Conditionals
if / elif / else, ternary
Section 9 · F-strings & Print Formatting
f-strings, format specs, print options
Section 10 · Random
random module, seeding, distributions
Section 11 · Algorithm Principles
Boolean zen, assertions, lookahead, fencepost, DeMorgan
Section 12 · File Processing
reading, writing, with-blocks, token/line based
Section 13 · Arrays (Lists as Arrays)
properties, value vs reference, 2-D arrays
Section 14 · Lists, Tuples & Dictionaries
collections deep dive, comprehensions
Section 15 · Objects & Classes
classes, __init__, static vs instance, state/behavior
Section 16 · The Big 4
abstraction, inheritance, polymorphism, encapsulation
Section 17 · Sorting & Searching
built-ins, algorithms, binary search
Section 18 · Recursion
base case, call stack, classic problems

—2—
Comprehensive Python Guide Sections 1–18 · All Skill Levels

—3—
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 1 Basics
print statements · output · what is Python?

What is Python?
Python is a high-level, interpreted, dynamically-typed, general-purpose programming language created by
Guido van Rossum and first released in 1991. It emphasises code readability — its syntax is designed to be
close to plain English — making it an excellent first language and a powerful tool for professionals.

Key CS Definitions
Programming Language
A formal set of rules (syntax) and meanings (semantics) used to instruct a computer.
Interpreted Language
Code is executed line-by-line by an interpreter at runtime, rather than compiled to machine code ahead of
time. Python uses CPython (the standard interpreter).
High-Level Language
Abstracts away hardware details (memory addresses, CPU registers). Opposite of assembly.
Dynamically Typed
Variable types are determined at runtime, not declared at compile-time. A variable can hold an int, then
later hold a string.
Syntax
The set of rules governing how programs must be written to be valid.
Semantics
The meaning of syntactically valid code — what it actually does.
Statement
A single instruction that Python can execute (e.g., a print call, an assignment).
Expression
A combination of values, variables, and operators that evaluates to a value (e.g., 2 + 3).

The print() Function


print() sends output to the console (stdout). It is the most common way to display information during
development.
# Basic print
print("Hello, World!")

# Multiple arguments — separated by a space by default


print("Hello", "World", "!")

—4—
Comprehensive Python Guide Sections 1–18 · All Skill Levels

# sep= changes the separator


print("2024", "04", "14", sep="-") # 2024-04-14

# end= changes what prints at the end (default is newline \n)


print("Loading", end="...")
print("done") # Loading...done

# Printing numbers and mixing types


print("The answer is", 42)
print(3.14)

# Printing nothing (blank line)


print()

Comments
Comments are ignored by Python. They exist for human readers.
# This is a single-line comment

x = 5 # Inline comment

"""
This is a multi-line string often used
as a block comment or docstring.
"""

Python Indentation
Python uses indentation (typically 4 spaces) to define code blocks. This is NOT optional — incorrect indentation
causes an IndentationError. Unlike C/Java there are no curly braces.
if True:
print("Inside the if block") # 4 spaces
print("Still inside")
print("Outside the if block")

■ PEP 8 (Python's style guide) recommends 4 spaces per indentation level. Never mix tabs and spaces.

Running Python
Python files use the .py extension. Run from a terminal with: python [Link] or python3 [Link].
Interactive mode is started by typing python3 with no arguments.

—5—
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 2 Data Types & Type Casting


int · float · bool · str · None · complex · casting

What is a Data Type?


A data type tells Python what kind of value a variable holds and what operations are legal on it. Python is
dynamically typed — you don't declare types, but every value has one. Use type(x) to inspect it at runtime.

Primitive / Built-in Types


int Whole numbers of arbitrary precision: 0, -5, 1_000_000

float 64-bit floating-point numbers: 3.14, -0.001, 2.0e8

bool Boolean: True or False (subclass of int — True==1, False==0)

str Immutable sequence of Unicode characters: 'hello', "world"

NoneType Represents the absence of a value. The only instance is None.

complex Complex numbers with real + imaginary parts: 3+4j

bytes Immutable sequence of bytes (integers 0–255): b'hello'

# Integer
age = 25
big = 1_000_000 # underscores allowed for readability
print(type(age)) # <class 'int'>

# Float
pi = 3.14159
scientific = 6.022e23 # Avogadro's number
print(type(pi)) # <class 'float'>

# Bool
is_student = True
passed = False
print(type(is_student)) # <class 'bool'>
print(True + True) # 2 (bool is a subtype of int!)

# String
name = 'Alice'
greeting = "Hello, World!"
multiline = """Line 1
Line 2
Line 3"""
print(type(name)) # <class 'str'>

—6—
Comprehensive Python Guide Sections 1–18 · All Skill Levels

# None
result = None
print(result is None) # True (use 'is', not ==)

# Complex
z = 2 + 3j
print([Link], [Link]) # 2.0 3.0

Variables & Assignment


A variable is a named reference to a value stored in memory. Assignment uses =. Python variables are labels,
not boxes — they point to objects.
x = 10
y = x # y points to the SAME integer object as x
x = 20 # x now points to a NEW object; y is unaffected
print(y) # 10

# Multiple assignment
a, b, c = 1, 2, 3
a = b = c = 0 # chain assignment

# Swap without temp variable


a, b = b, a

Type Casting (Type Conversion)


Implicit Casting (Coercion)
Python automatically converts types when needed: int + float → float.
Explicit Casting
You manually convert using built-in functions: int(), float(), str(), bool(), list(), etc.
# Implicit
result = 5 + 2.0 # int + float → float = 7.0

# Explicit — int()
print(int(3.9)) # 3 (truncates, does NOT round)
print(int("42")) # 42
print(int(True)) # 1
print(int("0b1010", 2))# 10 (binary string)

# Explicit — float()
print(float("3.14")) # 3.14
print(float(7)) # 7.0

# Explicit — str()
print(str(100)) # '100'
print(str(3.14)) # '3.14'
print(str(True)) # 'True'

—7—
Comprehensive Python Guide Sections 1–18 · All Skill Levels

# Explicit — bool()
# Falsy values: 0, 0.0, '', None, [], {}, ()
print(bool(0)) # False
print(bool(42)) # True
print(bool("")) # False
print(bool("hello")) # True

# Useful: round() vs int() vs [Link]()


import math
print(round(3.7)) # 4 (rounds to nearest)
print(int(3.7)) # 3 (truncates)
print([Link](3.9)) # 3
print([Link](3.1)) # 4

String Basics
s = "Hello, Python!"

# Indexing (0-based)
print(s[0]) # H
print(s[-1]) # ! (negative = from end)

# Slicing [start:stop:step] (stop is exclusive)


print(s[0:5]) # Hello
print(s[7:]) # Python!
print(s[:5]) # Hello
print(s[::2]) # Hlo yhn (every 2nd char)
print(s[::-1]) # !nohtyP ,olleH (reversed)

# Length
print(len(s)) # 14

# Common string methods


print([Link]()) # HELLO, PYTHON!
print([Link]()) # hello, python!
print([Link]()) # removes leading/trailing whitespace
print([Link]("Python", "World"))
print([Link](", ")) # ['Hello', 'Python!']
print("42".zfill(5)) # 00042

—8—
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 3 Loops
for · while · range · for-each · break · continue · else

Why Loops?
A loop is a control structure that repeats a block of code. Instead of writing the same statement 1000 times, a
loop iterates automatically.

Iteration
One execution of the loop body.
Iterable
Any object that can be looped over: list, string, range, dict, file, etc.
Iterator
An object that remembers its position in an iterable and produces the next value on each call to next().

for Loop
Python's for loop is a for-each loop — it iterates over each item in an iterable. There is no traditional C-style
for(int i=0; i<n; i++) in Python.
# Iterate over a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)

# Iterate over a string (each character)


for ch in "Python":
print(ch, end=" ") # P y t h o n

# Iterate over a range


for i in range(5): # 0 1 2 3 4
print(i, end=" ")

# Using enumerate() — gives index AND value


for i, fruit in enumerate(fruits):
print(i, fruit) # 0 apple, 1 banana, 2 cherry

# Using zip() — iterate two lists in parallel


names = ["Alice", "Bob", "Carol"]
scores = [95, 87, 91]
for name, score in zip(names, scores):
print(f"{name}: {score}")

range()

—9—
Comprehensive Python Guide Sections 1–18 · All Skill Levels

range() generates a sequence of integers. It is memory-efficient — it doesn't create a list, it generates numbers
on the fly.
range(stop) # 0, 1, ..., stop-1
range(start, stop) # start, start+1, ..., stop-1
range(start, stop, step) # step can be negative

for i in range(1, 11): # 1 through 10


print(i)

for i in range(10, 0, -2): # 10 8 6 4 2


print(i, end=" ")

# Convert to list if needed


print(list(range(5))) # [0, 1, 2, 3, 4]

while Loop
A while loop repeats as long as a condition is True. Use it when you don't know ahead of time how many
iterations are needed.
# Basic while
count = 0
while count < 5:
print(count)
count += 1 # IMPORTANT: update the condition variable

# Sentinel loop (read until a special value)


total = 0
while True:
value = int(input("Enter a number (0 to stop): "))
if value == 0:
break
total += value
print("Total:", total)

# User input validation


name = ""
while name == "":
name = input("Enter your name: ").strip()
print(f"Hello, {name}!")

break, continue, pass


# break — exit the loop immediately
for i in range(10):
if i == 5:
break
print(i, end=" ") # 0 1 2 3 4

# continue — skip the rest of this iteration


for i in range(10):

— 10 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

if i % 2 == 0:
continue
print(i, end=" ") # 1 3 5 7 9

# pass — do nothing (placeholder)


for i in range(5):
pass # empty loop body is a syntax error without pass

for / while … else


Python loops have an optional else clause. It runs when the loop finishes normally (no break). This is handy for
search problems.
# Search example: did we find x?
target = 7
for n in [1, 3, 5, 9]:
if n == target:
print("Found!")
break
else:
print("Not found") # runs because break was never hit

■ The else of a loop is NOT 'if the condition was False'. It means 'loop finished without break'.

— 11 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 4 Nested Structures & Scope


nested loops · nested if · LEGB · global · nonlocal

Nested Loops
A loop inside another loop. The inner loop runs its full cycle for each iteration of the outer loop. Useful for 2-D
structures like grids and matrices.
# Multiplication table
for i in range(1, 4):
for j in range(1, 4):
print(i * j, end="\t")
print() # newline after each row
# 1 2 3
# 2 4 6
# 3 6 9

# Nested list traversal


matrix = [[1,2,3],[4,5,6],[7,8,9]]
for row in matrix:
for val in row:
print(val, end=" ")
print()

Nested Conditionals
x = 15
if x > 0:
if x % 2 == 0:
print("positive even")
else:
print("positive odd") # ← printed
else:
print("non-positive")

# Often cleaner with 'and':


if x > 0 and x % 2 != 0:
print("positive odd")

Scope — LEGB Rule


Scope determines where a variable is visible. Python uses the LEGB rule to look up names:

L — Local Inside the current function.

E — Enclosing In any enclosing (outer) function (for nested functions).

G — Global At the top level of the module.

— 12 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

B — Built-in Names built into Python: print, len, range, etc.

x = "global" # Global scope

def outer():
x = "enclosing" # Enclosing scope

def inner():
x = "local" # Local scope
print(x) # local (L wins)

inner()
print(x) # enclosing (E wins inside outer)

outer()
print(x) # global

global keyword
To assign to a global variable inside a function, declare it with global. Without it, you'd create a new local
variable instead.
count = 0

def increment():
global count
count += 1

increment()
increment()
print(count) # 2

nonlocal keyword
Used in nested functions to modify a variable in the enclosing (but not global) scope.
def make_counter():
n = 0
def increment():
nonlocal n
n += 1
return n
return increment

c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3

■ Avoid overusing global/nonlocal. They make code harder to reason about. Prefer passing values as arguments
and returning results.

— 13 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 5 Operations
arithmetic · comparison · logical · bitwise · math library · shorthand

Arithmetic Operators
+ Addition: 5 + 3 = 8

- Subtraction: 5 - 3 = 2

* Multiplication: 5 * 3 = 15

/ True division: 7 / 2 = 3.5 (always returns float)

// Floor division: 7 // 2 = 3 (truncates toward negative infinity)

% Modulus (remainder): 7 % 2 = 1

** Exponentiation: 2 ** 10 = 1024

print(10 / 3) # 3.3333333333333335
print(10 // 3) # 3
print(-7 // 2) # -4 (floor toward -inf, NOT truncate!)
print(10 % 3) # 1
print(2 ** 8) # 256

# Useful modulus patterns


print(15 % 2 == 0) # False — odd number check
print(12 % 3 == 0) # True — divisible by 3

Comparison Operators
x, y = 5, 10
print(x == y) # False equal to
print(x != y) # True not equal
print(x < y) # True less than
print(x > y) # False greater than
print(x <= y) # True less than or equal
print(x >= y) # False greater than or equal

# Python allows chaining!


age = 25
print(18 <= age <= 65) # True

Logical Operators
# and, or, not
print(True and False) # False
print(True or False) # True
print(not True) # False

— 14 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

# Short-circuit evaluation
# 'and' stops at first False; 'or' stops at first True
x = 0
print(x != 0 and 10/x > 1) # False (10/x never evaluated!)

# Truthy/Falsy
print(bool([])) # False (empty list)
print(bool([1])) # True
print(42 or "hi") # 42 (returns first truthy value, not bool!)
print(0 or "hi") # 'hi'
print(0 and "hi") # 0 (returns first falsy value)

Augmented Assignment (Shorthand)


x = 10
x += 3 # x = x + 3 → 13
x -= 2 # → 11
x *= 4 # → 44
x //= 5 # → 8
x **= 2 # → 64
x %= 10 # → 4
x /= 2 # → 2.0

# String concatenation shorthand


s = "Hello"
s += " World" # s = "Hello World"

Bitwise Operators
# Operate on integer bits
print(5 & 3) # 1 AND: 0101 & 0011 = 0001
print(5 | 3) # 7 OR: 0101 | 0011 = 0111
print(5 ^ 3) # 6 XOR: 0101 ^ 0011 = 0110
print(~5) # -6 NOT: flips all bits
print(5 << 1) # 10 left shift (multiply by 2)
print(5 >> 1) # 2 right shift (integer divide by 2)

The math Library


import math

print([Link]) # 3.141592653589793
print(math.e) # 2.718281828459045
print([Link](144)) # 12.0
print([Link](2, 10)) # 1024.0 (returns float)
print([Link](100, 10)) # 2.0 log base 10
print(math.log2(64)) # 6.0
print([Link](math.e)) # 1.0 natural log
print([Link](-3.2)) # -4
print([Link](-3.2)) # -3

— 15 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

print([Link](-9)) # 9.0 absolute value as float


print(abs(-9)) # 9 built-in abs()
print([Link](5)) # 120
print([Link](48, 18)) # 6
print([Link]([Link]/2)) # 1.0
print([Link](3, 4)) # 5.0 (sqrt(3^2 + 4^2))
print([Link]) # inf
print([Link](float('nan'))) # True

Operator Precedence (highest → lowest)


** → unary +/- → * / // % → + - → comparisons → not → and → or → =
■ When in doubt, use parentheses to make intent explicit.

— 16 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 6 Methods & Parameters


functions · parameters · arguments · return · overloading · signatures

Defining Functions
Function
A named, reusable block of code that performs a specific task. Defined with def.
Parameter
A variable in the function definition (the placeholder).
Argument
The actual value passed when calling the function.
Return Value
The value the function hands back to the caller via return. Without return, Python returns None.
def greet(name): # 'name' is a parameter
return f"Hello, {name}!"

msg = greet("Alice") # "Alice" is an argument


print(msg) # Hello, Alice!

# No return → returns None


def say_hi():
print("Hi!")

result = say_hi() # prints "Hi!"


print(result) # None

Multiple Parameters & Default Values


def power(base, exponent=2): # exponent has a default
return base ** exponent

print(power(3)) # 9 (exponent defaults to 2)


print(power(3, 3)) # 27
print(power(2, 10)) # 1024

# Multiple return values (actually returns a tuple)


def min_max(nums):
return min(nums), max(nums)

lo, hi = min_max([3,1,9,5])
print(lo, hi) # 1 9

Positional vs Keyword Arguments

— 17 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

def describe_pet(name, animal="dog", age=1):


print(f"{name} is a {age}-year-old {animal}")

describe_pet("Buddy") # positional + defaults


describe_pet("Whiskers", "cat", 3) # all positional
describe_pet("Rex", age=5) # keyword arg
describe_pet(age=2, name="Nemo", animal="fish") # all keyword

*args and **kwargs


# *args: arbitrary positional arguments → tuple
def total(*args):
return sum(args)

print(total(1, 2, 3)) # 6
print(total(1, 2, 3, 4, 5)) # 15

# **kwargs: arbitrary keyword arguments → dict


def display(**kwargs):
for key, val in [Link]():
print(f"{key} = {val}")

display(name="Alice", age=25, city="Seattle")

# Combining them
def mixed(required, *args, **kwargs):
print(required, args, kwargs)

mixed("hi", 1, 2, 3, x=10, y=20)


# hi (1, 2, 3) {'x': 10, 'y': 20}

Function Signatures & 'Overloading'


A function's signature is its name plus its parameters. Python does NOT support traditional method overloading
(same name, different parameter types). Instead, Python achieves flexibility via default parameters,
*args/**kwargs, and isinstance() checks.
# Simulating overloading with defaults
def area(length, width=None):
if width is None:
return length ** 2 # square
return length * width # rectangle

print(area(5)) # 25
print(area(4, 6)) # 24

# Using isinstance()
def double(x):
if isinstance(x, str):
return x * 2 # "hi" → "hihi"
return x * 2 # 5 → 10

— 18 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Docstrings
def celsius_to_fahrenheit(c):
"""
Convert Celsius to Fahrenheit.

Args:
c (float): Temperature in Celsius.

Returns:
float: Temperature in Fahrenheit.

Example:
>>> celsius_to_fahrenheit(100)
212.0
"""
return c * 9/5 + 32

help(celsius_to_fahrenheit) # displays the docstring

■ Good functions do ONE thing, have a clear name, and are under ~20 lines. This is the Single Responsibility
Principle.

Lambda (Anonymous) Functions


# lambda parameters: expression
square = lambda x: x ** 2
print(square(5)) # 25

# Useful with sorted(), map(), filter()


nums = [3, 1, 4, 1, 5, 9]
print(sorted(nums, key=lambda x: -x)) # descending

words = ["banana", "apple", "cherry"]


print(sorted(words, key=lambda w: len(w))) # by length

— 19 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 7 Input
input() · tokens · parsing · type conversion · validation

The input() Function


input() reads a line from the user (stdin) and returns it as a string. The optional argument is a prompt displayed
to the user.
name = input("What is your name? ")
print(f"Hello, {name}!")

# input() ALWAYS returns a string — cast as needed


age_str = input("Enter your age: ")
age = int(age_str) # explicit cast

# More concisely:
age = int(input("Enter your age: "))

Tokens
Token
A meaningful unit of data in a string, separated by whitespace (or another delimiter). '3 hello 4.5' has three
tokens: '3', 'hello', '4.5'.
# Reading multiple tokens on one line
line = input("Enter two numbers: ") # e.g., "7 13"
tokens = [Link]() # ['7', '13']
a = int(tokens[0])
b = int(tokens[1])
print(a + b)

# More Pythonic — split + unpack


a, b = input("Two numbers: ").split()
a, b = int(a), int(b)

# Reading floats
x, y = map(float, input("Two floats: ").split())
print(x + y)

map() for Parsing


map(function, iterable) applies a function to every element. Combine with split() to parse multiple tokens at
once.
# Read 5 integers on one line
numbers = list(map(int, input("5 integers: ").split()))
print(sum(numbers))

— 20 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

# Read unknown count of integers


data = list(map(int, input().split()))
print(sorted(data))

Multi-line Input
# Reading n lines
n = int(input())
values = []
for _ in range(n):
[Link](int(input()))
print(sum(values))

# Reading a grid
rows, cols = map(int, input().split())
grid = []
for _ in range(rows):
row = list(map(int, input().split()))
[Link](row)

Input Validation
# Using a while loop + try/except to ensure valid input
while True:
try:
age = int(input("Enter your age (0-120): "))
if 0 <= age <= 120:
break
else:
print("Age must be between 0 and 120.")
except ValueError:
print("Please enter a whole number.")

print(f"Valid age: {age}")

■ Always validate user input before using it. Never assume the user will enter data in the correct format.

— 21 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 8 Conditionals
if · elif · else · ternary · match-case

if / elif / else
Conditionals let the program choose between paths of execution based on Boolean expressions.
score = 85

if score >= 90:


grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"

print(f"Grade: {grade}") # Grade: B

# Only one branch executes — Python checks top to bottom


# and stops at the FIRST True condition.

Nested Conditionals
x = 10
if x > 0:
if x < 100:
print("small positive")
else:
print("large positive")
else:
print("non-positive")

# Equivalent using 'and'


if 0 < x < 100:
print("small positive")

Ternary (Conditional Expression)


A compact way to write a simple if/else in one line.
# value_if_true if condition else value_if_false
age = 20
status = "adult" if age >= 18 else "minor"
print(status) # adult

— 22 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

# Can be used in any expression context


x = 7
abs_x = x if x >= 0 else -x
print(abs_x) # 7

match / case (Python 3.10+)


Python 3.10 introduced structural pattern matching, similar to switch/case in other languages but far more
powerful.
command = "quit"

match command:
case "quit":
print("Quitting...")
case "help":
print("Available commands: quit, help, start")
case "start":
print("Starting...")
case _: # default (wildcard)
print(f"Unknown command: {command}")

# Matching on values and types


point = (1, 0)
match point:
case (0, 0):
print("Origin")
case (x, 0):
print(f"On X-axis at {x}")
case (0, y):
print(f"On Y-axis at {y}")
case (x, y):
print(f"Point at ({x}, {y})")

Common Conditional Patterns


# Check membership
if "alice" in ["alice", "bob", "carol"]:
print("found")

# Check if None
value = None
if value is None:
print("no value")
if value is not None:
print("has value")

# Truthy/Falsy checks
name = ""
if not name:

— 23 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

print("Name is empty")

items = []
if items:
print("Has items")
else:
print("Empty list")

— 24 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 9 F-strings & Print Formatting


f-strings · format specs · [Link]() · print options

F-strings (f-literals)
Introduced in Python 3.6. Prefix a string with f or F and embed expressions in {}.
name = "Alice"
age = 30
pi = 3.14159

print(f"Hello, {name}!") # Hello, Alice!


print(f"Age: {age}") # Age: 30
print(f"2 + 2 = {2 + 2}") # 2 + 2 = 4
print(f"Upper: {[Link]()}") # Upper: ALICE
print(f"Pi is approximately {pi:.2f}") # Pi is approximately 3.14

Format Specifications
Inside the braces: {value:format_spec}. Format spec syntax:
[[fill]align][sign][#][0][width][grouping][.precision][type]
x = 3.14159265

# Floating point precision


print(f"{x:.2f}") # 3.14 (2 decimal places)
print(f"{x:.4f}") # 3.1416
print(f"{x:.0f}") # 3 (no decimals)
print(f"{x:10.2f}") # ' 3.14' (width 10, right-aligned)
print(f"{x:<10.2f}") # '3.14 ' (left-aligned)
print(f"{x:^10.2f}") # ' 3.14 ' (centered)
print(f"{x:+.2f}") # +3.14 (force sign)
print(f"{x:010.2f}") # 0000003.14 (zero-padded)

# Scientific notation
print(f"{12345.6789:.2e}") # 1.23e+04

# Integers
n = 255
print(f"{n:d}") # 255 (decimal)
print(f"{n:b}") # 11111111 (binary)
print(f"{n:o}") # 377 (octal)
print(f"{n:x}") # ff (hex lower)
print(f"{n:X}") # FF (hex upper)
print(f"{n:#x}") # 0xff (with prefix)
print(f"{n:08b}") # 11111111 (zero-padded binary)

# Strings

— 25 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

s = "hello"
print(f"{s:>10}") # ' hello' right-aligned in width 10
print(f"{s:*^10}") # '**hello***' centered with * fill

# Thousands separator
big = 1234567
print(f"{big:,}") # 1,234,567
print(f"{big:_}") # 1_234_567

Debugging with f-strings (Python 3.8+)


x = 42
print(f"{x=}") # x=42 (prints name AND value!)
print(f"{x*2=}") # x*2=84

[Link]() Method
# Positional
print("Hello, {}! You are {} years old.".format("Bob", 25))

# Named
print("{name} scored {score}%".format(name="Alice", score=95))

# Format spec works the same way


print("{:.3f}".format(3.14159)) # 3.142

% Formatting (legacy)
# Old C-style formatting — still seen in older code
print("Hello, %s! You are %d years old." % ("Bob", 25))
print("Pi = %.4f" % 3.14159)

■ Use f-strings for new code. They are faster, more readable, and more powerful.

— 26 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 10 Random
random module · seeding · distributions · secrets

Importing random
import random

Core Functions
import random

# Random float in [0.0, 1.0)


print([Link]()) # e.g., 0.7324...

# Random float in [a, b]


print([Link](1.0, 10.0)) # e.g., 6.842...

# Random integer in [a, b] (both inclusive)


print([Link](1, 6)) # e.g., 4 (die roll)

# Random integer in range(start, stop, step) — stop EXCLUDED


print([Link](0, 100, 5)) # multiples of 5: 0,5,...,95

# Choose one element randomly from a sequence


colors = ["red", "green", "blue"]
print([Link](colors))

# Choose k elements WITH replacement


print([Link](colors, k=5))

# Choose k elements WITHOUT replacement


print([Link](colors, k=2))

# Shuffle a list IN PLACE


deck = list(range(1, 53))
[Link](deck)
print(deck[:5])

Seeding
A seed initialises the random number generator. With the same seed you get the same sequence every time —
essential for reproducibility in testing and simulations.
[Link](42)
print([Link](1, 100)) # always 82 with seed 42
print([Link](1, 100)) # always 15

[Link](42) # reset

— 27 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

print([Link](1, 100)) # 82 again

# Without seed: different each run (uses system time)


[Link]() # or just don't call seed()

Weighted Choices
# weights don't need to sum to 1
outcomes = ["win", "lose", "draw"]
weights = [1, 3, 1] # 20% win, 60% lose, 20% draw
print([Link](outcomes, weights=weights, k=10))

Gaussian Distribution
# Gaussian (normal) distribution: mu=mean, sigma=std dev
heights = [[Link](170, 10) for _ in range(5)]
print([f"{h:.1f}" for h in heights])

secrets Module (Cryptographically Secure)


For passwords, tokens, and anything security-sensitive — DO NOT use random.
import secrets

# Secure random integer


print([Link](100)) # 0 to 99

# Secure random bytes


token_bytes = secrets.token_bytes(16)

# URL-safe token (for password reset links, etc.)


token = secrets.token_urlsafe(32)
print(token)

— 28 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 11 Algorithm Principles


Boolean zen · assertions · lookahead · fencepost · DeMorgan's Law

Boolean Zen
Avoid comparing booleans to True/False — the boolean IS the condition.
is_valid = True

# BAD — redundant
if is_valid == True:
pass

# GOOD
if is_valid:
pass

# BAD
if found == False:
pass

# GOOD
if not found:
pass

# BAD — returning boolean literal


def is_even(n):
if n % 2 == 0:
return True
else:
return False

# GOOD
def is_even(n):
return n % 2 == 0

Assertions
An assertion is a claim that something must be true at a specific point in the program. Used for debugging and
testing preconditions/postconditions.

assert expression, message


If expression is False, raises AssertionError with the optional message.
def divide(a, b):
assert b != 0, f"Division by zero! b={b}"
return a / b

— 29 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

print(divide(10, 2)) # 5.0


# divide(10, 0) # AssertionError: Division by zero! b=0

# Checking postconditions
def sort_list(lst):
result = sorted(lst)
assert result == sorted(result), "Result is not sorted!"
return result

■ Assertions can be disabled with python -O flag. Do NOT use them for user input validation — use if/raise instead.

Fencepost Problem
The fencepost problem (also called off-by-one error) arises when you confuse the number of posts vs the
number of gaps between posts.
# WRONG: prints comma after last element
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit, end=", ")
# apple, banana, cherry, ← extra comma!

# CORRECT pattern 1: print separator BEFORE all but first


for i, fruit in enumerate(fruits):
if i > 0:
print(", ", end="")
print(fruit, end="")
print() # apple, banana, cherry

# CORRECT pattern 2: use [Link]()


print(", ".join(fruits)) # apple, banana, cherry

# Fencepost in range:
# A fence 10 meters long with posts every 2 meters
# has 6 posts but only 5 gaps
fence_length = 10
spacing = 2
posts = fence_length // spacing + 1 # 6, NOT 5
gaps = fence_length // spacing # 5

Lookahead
A lookahead reads or inspects the NEXT element before consuming the current one. Useful for processing
sequences that require context from adjacent items.
# Detect consecutive duplicates
nums = [1, 2, 2, 3, 4, 4, 4, 5]
for i in range(len(nums) - 1): # -1 = lookahead safety
if nums[i] == nums[i+1]:
print(f"Duplicate at index {i}: {nums[i]}")

# Lookahead in file processing (peek at next line)

— 30 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

lines = ["hello", "world", "end", "ignore"]


for i in range(len(lines) - 1):
if lines[i+1] == "end":
print(f"Last real line: {lines[i]}")
break

DeMorgan's Laws
DeMorgan's Laws allow you to rewrite complex logical expressions. Invaluable for simplifying conditions.

not (A and B) ≡ (not A) or (not B)

not (A or B) ≡ (not A) and (not B)

# Example: "not (x > 5 and y < 10)"


# By DeMorgan: "(x <= 5 or y >= 10)"

x, y = 3, 8

# Original
if not (x > 5 and y < 10):
print("DeMorgan 1")

# Equivalent (by DeMorgan)


if x <= 5 or y >= 10:
print("DeMorgan 1")

# Both print the same!

# Practical: loop until (not valid and not done)


# = loop while (valid or not done) ← DeMorgan helps simplify

Invariants & Preconditions/Postconditions


Precondition
What must be true BEFORE a function runs (inputs, state).
Postcondition
What is guaranteed to be true AFTER a function runs (output, state changes).
Invariant
A condition that remains true throughout a computation (e.g., a loop invariant: 'at the start of each iteration,
total equals the sum of the first i elements').

— 31 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 12 File Processing


reading · writing · with-blocks · token-based · line-based · CSV

Opening & Closing Files


File Handle
An object returned by open() that represents an open file.

'r' Read (default). File must exist.

'w' Write. Creates or OVERWRITES.

'a' Append. Creates or adds to end.

'x' Exclusive create. Fails if file exists.

'rb' Read binary (images, PDFs, etc.)

'w+' Read and write.

# ALWAYS use 'with' — auto-closes the file even on error


with open("[Link]", "r") as f:
content = [Link]() # read entire file as one string
print(content)

# Without 'with' (manual — error-prone)


f = open("[Link]", "r")
content = [Link]()
[Link]() # must close manually!

Line-Based Reading
# Method 1: readline() — one line at a time
with open("[Link]") as f:
line = [Link]()
while line:
print([Link]()) # strip() removes trailing \n
line = [Link]()

# Method 2: readlines() — all lines into a list


with open("[Link]") as f:
lines = [Link]() # [line1, line2, ...]

# Method 3: iterate directly (best — memory efficient!)


with open("[Link]") as f:
for line in f:
print([Link]())

— 32 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Token-Based Reading
# Read file word by word
with open("[Link]") as f:
for line in f:
tokens = [Link]()
for token in tokens:
print(token)

# Read integers from a file


with open("[Link]") as f:
for line in f:
for token in [Link]():
n = int(token)
print(n * 2)

Writing to Files
# Write mode — creates or overwrites
with open("[Link]", "w") as f:
[Link]("Hello\n")
[Link]("World\n")
print("From print", file=f) # redirect print to file

# Append mode
with open("[Link]", "a") as f:
[Link]("New log entry\n")

# Writing multiple lines at once


lines = ["line 1\n", "line 2\n", "line 3\n"]
with open("[Link]", "w") as f:
[Link](lines)

CSV Files
import csv

# Reading CSV
with open("[Link]", "r", newline="") as f:
reader = [Link](f)
header = next(reader) # skip header row
for row in reader:
print(row) # each row is a list

# Writing CSV
data = [
["Alice", 95, "A"],
["Bob", 82, "B"],
]
with open("[Link]", "w", newline="") as f:
writer = [Link](f)
[Link](["Name", "Score", "Grade"])

— 33 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

[Link](data)

# DictReader — rows as dicts


with open("[Link]") as f:
reader = [Link](f)
for row in reader:
print(row["Name"], row["Grade"])

Working with Paths


import os
from pathlib import Path

# Check if file exists


if [Link]("[Link]"):
print("File found")

# Using pathlib (modern, recommended)


p = Path("[Link]")
if [Link]():
print(f"Size: {[Link]().st_size} bytes")

# Read entire file


text = p.read_text(encoding="utf-8")

# Write entire file


p.write_text("Hello, World!", encoding="utf-8")

■ Always handle FileNotFoundError when opening files that may not exist: try: ... except FileNotFoundError:
print('File not found')

— 34 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 13 Arrays (Lists as Arrays)


list properties · value vs reference semantics · 2-D arrays · array module

Arrays in Python
Python does not have a built-in array type in the traditional sense. Lists serve as dynamic arrays. For fixed-type
numeric arrays, use the array module or NumPy. This section covers list-as-array patterns you'll see in CS
coursework.

Array
A contiguous collection of elements accessible by integer index. O(1) access by index.
Dynamic Array
An array that can grow; Python lists are dynamic arrays. Appending is amortised O(1).

List Properties
nums = [10, 20, 30, 40, 50]

# Length
print(len(nums)) # 5

# Indexing — 0-based
print(nums[0]) # 10 first element
print(nums[-1]) # 50 last element
print(nums[-2]) # 40

# Slicing — creates a NEW list (shallow copy)


print(nums[1:4]) # [20, 30, 40]
print(nums[:3]) # [10, 20, 30]
print(nums[::2]) # [10, 30, 50]

# Mutation
nums[2] = 99
print(nums) # [10, 20, 99, 40, 50]

# Common operations
[Link](60) # add to end
[Link](0, 5) # insert at index
[Link](99) # remove first occurrence of value
popped = [Link]() # remove & return last
popped2 = [Link](1) # remove & return at index
[Link]() # in-place sort
[Link]() # in-place reverse
print([Link](40)) # index of first 40
print([Link](20)) # occurrences of 20

— 35 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Value vs Reference Semantics


This is one of the most important distinctions in Python.

Value Semantics
Primitives (int, float, bool, str, tuple) behave as if copied on assignment. Assigning to a new variable does
not affect the original.
Reference Semantics
Lists (and other mutable objects) are passed by reference — multiple variables can point to the SAME list
object.
# Value semantics with ints
a = 5
b = a # b gets a COPY of the value
b = 10
print(a) # 5 — unchanged!

# Reference semantics with lists


x = [1, 2, 3]
y = x # y points to the SAME list as x
y[0] = 99
print(x) # [99, 2, 3] — x is affected!

# To make a TRUE COPY:


z = [Link]() # shallow copy
z = x[:] # also shallow copy
z = list(x) # also shallow copy
import copy
z = [Link](x) # deep copy (for nested structures)

# Checking identity vs equality


a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True (same values)
print(a is b) # False (different objects)
print(a is c) # True (same object!)

Passing Lists to Functions


def zero_first(lst):
lst[0] = 0 # modifies the ORIGINAL list!

nums = [1, 2, 3]
zero_first(nums)
print(nums) # [0, 2, 3]

# If you want to avoid modifying the original:


def safe_zero_first(lst):
result = [Link]()
result[0] = 0
return result

— 36 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

2-D Arrays (Nested Lists)


# Create manually
grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(grid[1][2]) # 6 (row 1, col 2)

# Create with list comprehension


rows, cols = 3, 4
matrix = [[0] * cols for _ in range(rows)]
# [[0,0,0,0],[0,0,0,0],[0,0,0,0]]

# WARNING: Do NOT use [[0]*cols]*rows — all rows share the same list!

# Traverse
for row in matrix:
for val in row:
print(val, end=" ")
print()

# Traverse with indices


for r in range(len(grid)):
for c in range(len(grid[r])):
print(grid[r][c], end=" ")
print()

Advanced List Techniques


# List comprehension
squares = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# With condition
evens = [x for x in range(20) if x % 2 == 0]

# Nested comprehension (flatten a matrix)


matrix = [[1,2,3],[4,5,6]]
flat = [val for row in matrix for val in row]
# [1, 2, 3, 4, 5, 6]

# Filter and transform


names = ["Alice", "Bob", "Charlie", "Dave"]
long_upper = [[Link]() for n in names if len(n) > 3]
# ['ALICE', 'CHARLIE', 'DAVE']

# map/filter equivalents
squares2 = list(map(lambda x: x**2, range(10)))
odds = list(filter(lambda x: x%2 != 0, range(10)))

— 37 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

# Useful builtins on lists


nums = [3, 1, 4, 1, 5, 9, 2, 6]
print(min(nums), max(nums), sum(nums))
print(sorted(nums)) # new sorted list
print(sorted(nums, reverse=True))
print(any(x > 8 for x in nums)) # True (9 > 8)
print(all(x > 0 for x in nums)) # True

— 38 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 14 Lists, Tuples & Dictionaries


deep dive · sets · comprehensions · common patterns

Lists (recap + advanced)


# Lists are ordered, mutable, allow duplicates
primes = [2, 3, 5, 7, 11]
[Link]([13, 17, 19]) # add multiple
print(len(primes)) # 8

# Membership test — O(n)


print(7 in primes) # True

# Unpacking
a, b, *rest = primes
print(a, b, rest) # 2 3 [5, 7, 11, 13, 17, 19]

# Zip two lists into list of tuples


keys = ["a", "b", "c"]
vals = [1, 2, 3]
pairs = list(zip(keys, vals)) # [('a',1),('b',2),('c',3)]

Tuples
Tuples are ordered, IMMUTABLE sequences. Once created, they cannot be changed. They are faster than lists
and hashable (can be used as dictionary keys or set elements).
# Tuple creation
point = (3, 4)
single = (42,) # trailing comma REQUIRED for single-element tuple
empty = ()

# Indexing and slicing (same as list)


print(point[0]) # 3
print(point[-1]) # 4

# Unpacking
x, y = point
print(x, y) # 3 4

# Tuples are immutable


# point[0] = 99 ← TypeError!

# Use tuples for fixed data: coordinates, RGB, dates


RGB_RED = (255, 0, 0)
DB_CONFIG = ("localhost", 5432, "mydb")

# Named tuples — structured tuples with named fields

— 39 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

from collections import namedtuple


Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4
print(p) # Point(x=3, y=4)

Dictionaries
Dictionaries map unique keys to values. Implemented as hash tables — O(1) average lookups, insertions, and
deletions.
# Creation
student = {"name": "Alice", "age": 20, "gpa": 3.8}
empty = {}
also = dict(name="Bob", age=22)

# Access
print(student["name"]) # Alice
print([Link]("major")) # None (no KeyError)
print([Link]("major", "Undeclared")) # Undeclared

# Mutation
student["age"] = 21
student["major"] = "CS"
del student["gpa"]

# Iteration
for key in student:
print(key, student[key])

for key, val in [Link]():


print(f"{key}: {val}")

print(list([Link]()))
print(list([Link]()))

# Membership (checks keys only)


print("name" in student) # True

# Default values with setdefault / defaultdict


[Link]("year", 1) # only sets if key absent

from collections import defaultdict


word_count = defaultdict(int)
for word in "the cat sat on the mat the cat".split():
word_count[word] += 1
print(dict(word_count)) # {'the':3,'cat':2,'sat':1,'on':1,'mat':1}

Dictionary Comprehensions

— 40 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

# {key_expr: val_expr for item in iterable}


squares = {x: x**2 for x in range(6)}
# {0:0, 1:1, 2:4, 3:9, 4:16, 5:25}

# Invert a dict (only safe if values are unique)


original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in [Link]()}
# {1:'a', 2:'b', 3:'c'}

# Filter
passing = {name: score
for name, score in {"Alice":95,"Bob":55,"Carol":78}.items()
if score >= 60}

Sets
Sets are unordered collections of unique elements. Great for membership tests and set operations.
# Creation
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
s = set() # empty set (NOT {} which is an empty dict!)

# Membership — O(1)
print(3 in a) # True

# Set operations
print(a | b) # union: {1,2,3,4,5,6}
print(a & b) # intersection: {3,4}
print(a - b) # difference: {1,2}
print(a ^ b) # symmetric diff: {1,2,5,6}

# Remove duplicates from a list


nums = [1, 2, 2, 3, 3, 3]
unique = list(set(nums))
print(sorted(unique)) # [1, 2, 3]

# Set comprehension
evens = {x for x in range(20) if x % 2 == 0}

— 41 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 15 Objects & Classes


OOP basics · __init__ · self · static vs instance · state & behavior

Object-Oriented Programming
Object
A bundle of data (attributes) and behaviour (methods).
Class
A blueprint/template for creating objects.
Instance
A concrete object created from a class.
Attribute
A variable belonging to an object (state).
Method
A function belonging to a class (behaviour).
self
The first parameter of every instance method; refers to the instance itself.

Defining a Class
class Dog:
# Class variable — shared by ALL instances
species = "Canis lupus familiaris"

# Constructor — called when Dog() is invoked


def __init__(self, name, age, breed):
# Instance variables — unique to each object
[Link] = name
[Link] = age
[Link] = breed

# Instance method
def bark(self):
return f"{[Link]} says: Woof!"

def birthday(self):
[Link] += 1

# String representation
def __str__(self):
return f"Dog({[Link]}, {[Link]}yo, {[Link]})"

def __repr__(self):
return f"Dog(name={[Link]!r}, age={[Link]}, breed={[Link]!r})"

— 42 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

# Creating instances
d1 = Dog("Rex", 3, "German Shepherd")
d2 = Dog("Fluffy", 1, "Poodle")

print([Link]()) # Rex says: Woof!


print([Link]) # Fluffy
[Link]()
print([Link]) # 4
print(d1) # Dog(Rex, 4yo, German Shepherd)
print([Link]) # Canis lupus familiaris (class variable)

Static vs Instance
Instance variable
Defined with self.x. Each instance has its own copy.
Class variable
Defined at class level (no self). Shared across all instances.
Instance method
Takes self. Operates on instance data.
Class method
Decorated with @classmethod. Takes cls. Operates on class data.
Static method
Decorated with @staticmethod. No self or cls. Utility function.
class Counter:
total_created = 0 # class variable

def __init__(self, start=0):


[Link] = start # instance variable
Counter.total_created += 1

def increment(self): # instance method


[Link] += 1

@classmethod
def get_total(cls): # class method
return cls.total_created

@staticmethod
def description(): # static method
return "A simple counter"

c1 = Counter()
c2 = Counter(10)
[Link]()

print([Link]) # 1

— 43 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

print([Link]) # 10
print(Counter.get_total()) # 2
print([Link]())

Special (Dunder) Methods


class Vector:
def __init__(self, x, y):
self.x, self.y = x, y

def __str__(self): # str(v)


return f"({self.x}, {self.y})"

def __repr__(self): # developer repr


return f"Vector({self.x}, {self.y})"

def __add__(self, other): # v1 + v2


return Vector(self.x + other.x, self.y + other.y)

def __mul__(self, scalar): # v * 3


return Vector(self.x * scalar, self.y * scalar)

def __len__(self): # len(v)


return 2

def __eq__(self, other): # v1 == v2


return self.x == other.x and self.y == other.y

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # (4, 6)
print(v1 * 3) # (3, 6)
print(v1 == v2) # False

— 44 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 16 The Big 4


Abstraction · Inheritance · Polymorphism · Encapsulation

1. Encapsulation
Encapsulation
Bundling data and the methods that operate on that data within one unit (class), and restricting direct
access to internal state. Protects invariants.
class BankAccount:
def __init__(self, owner, balance=0):
[Link] = owner
self._balance = balance # convention: _ = 'private'

# Getter
@property
def balance(self):
return self._balance

# Setter with validation


@[Link]
def balance(self, amount):
if amount < 0:
raise ValueError("Balance cannot be negative")
self._balance = amount

def deposit(self, amount):


if amount <= 0:
raise ValueError("Deposit must be positive")
self._balance += amount

def withdraw(self, amount):


if amount > self._balance:
raise ValueError("Insufficient funds")
self._balance -= amount
return amount

acc = BankAccount("Alice", 1000)


[Link](500)
print([Link]) # 1500
# acc._balance = -999 # Possible but bad practice — convention says don't

2. Inheritance
Inheritance
A class (child/subclass) derives attributes and methods from another class (parent/superclass). Enables
code reuse and is-a relationships.

— 45 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

class Animal:
def __init__(self, name, age):
[Link] = name
[Link] = age

def eat(self):
print(f"{[Link]} is eating.")

def speak(self):
raise NotImplementedError("Subclass must implement speak()")

def __str__(self):
return f"{type(self).__name__}({[Link]}, {[Link]}yo)"

class Dog(Animal): # Dog inherits from Animal


def __init__(self, name, age, breed):
super().__init__(name, age) # call parent __init__
[Link] = breed

def speak(self): # override parent method


return "Woof!"

def fetch(self): # new method


return f"{[Link]} fetches the ball!"

class Cat(Animal):
def speak(self):
return "Meow!"

d = Dog("Rex", 3, "Lab")
c = Cat("Whiskers", 5)

print(d) # Dog(Rex, 3yo)


print([Link]()) # Woof!
[Link]() # Rex is eating. (inherited)
print([Link]()) # Rex fetches the ball!
print(isinstance(d, Dog)) # True
print(isinstance(d, Animal)) # True (is-a relationship!)

3. Polymorphism
Polymorphism
The ability of different objects to respond to the same message (method call) in different ways. 'Many
forms'. Python achieves this through method overriding and duck typing.
# All Animals can speak(), but each does so differently
animals = [Dog("Rex",3,"Lab"), Cat("Luna",2), Dog("Buddy",1,"Poodle")]

for animal in animals: # treat all as Animal


print([Link]()) # calls the RIGHT version!
# Woof!
# Meow!

— 46 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

# Woof!

# Duck typing — Python's native polymorphism


# "If it walks like a duck and quacks like a duck, it's a duck"
class Duck:
def speak(self): return "Quack!"
class Robot:
def speak(self): return "Beep boop"

# Neither inherits from Animal, but we can use them the same way:
things = [Duck(), Robot()]
for t in things:
print([Link]()) # Quack! Beep boop

4. Abstraction
Abstraction
Hiding complex implementation details and exposing only the essential interface. Users know WHAT
something does, not HOW it does it. Achieved via abstract base classes and clean interfaces.
from abc import ABC, abstractmethod

class Shape(ABC): # Abstract Base Class


@abstractmethod
def area(self): # must be implemented by subclasses
pass

@abstractmethod
def perimeter(self):
pass

def describe(self): # concrete method


return (f"I am a {type(self).__name__} with "
f"area={[Link]():.2f}")

class Circle(Shape):
def __init__(self, radius):
[Link] = radius
def area(self):
import math
return [Link] * [Link] ** 2
def perimeter(self):
import math
return 2 * [Link] * [Link]

class Rectangle(Shape):
def __init__(self, w, h):
self.w, self.h = w, h
def area(self):
return self.w * self.h
def perimeter(self):

— 47 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

return 2 * (self.w + self.h)

shapes = [Circle(5), Rectangle(4, 6)]


for s in shapes:
print([Link]())
# s = Shape() ← TypeError: Can't instantiate abstract class

— 48 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 17 Sorting & Searching


built-ins · selection/bubble/insertion/merge/quick sort · binary search · Big O

Big O Notation (Complexity)


Big O
Describes how runtime or space grows relative to input size n.

O(1) Constant — doesn't grow: dict lookup, list index

O(log n) Logarithmic — very fast: binary search

O(n) Linear — proportional: linear search, single loop

O(n log n) Linearithmic: merge sort, Tim sort (Python's sort)

O(n²) Quadratic — slow: bubble sort, nested loops on n

O(2■) Exponential — very slow: naive recursion

Python Built-in Sorting


nums = [5, 2, 8, 1, 9, 3]

# sorted() — returns NEW list


print(sorted(nums)) # [1,2,3,5,8,9]
print(sorted(nums, reverse=True))# [9,8,5,3,2,1]

# [Link]() — IN PLACE, returns None


[Link]()
print(nums) # [1,2,3,5,8,9]

# Sorting with key


words = ["banana", "apple", "fig", "cherry"]
print(sorted(words, key=len)) # by length
print(sorted(words, key=[Link])) # case-insensitive

# Sorting objects
students = [("Alice",95), ("Bob",82), ("Carol",91)]
[Link](key=lambda s: s[1], reverse=True)
print(students) # [('Alice',95),('Carol',91),('Bob',82)]

# Python's sort is Timsort — O(n log n) worst case,


# O(n) best case on nearly-sorted data.

Linear Search — O(n)

— 49 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

def linear_search(lst, target):


"""Return index of target or -1 if not found."""
for i, val in enumerate(lst):
if val == target:
return i
return -1

nums = [4, 2, 7, 1, 9, 3]
print(linear_search(nums, 7)) # 2
print(linear_search(nums, 5)) # -1

Binary Search — O(log n)


Binary search requires a SORTED list. Repeatedly halves the search space.
def binary_search(lst, target):
"""Return index of target in sorted lst, or -1."""
low, high = 0, len(lst) - 1
while low <= high:
mid = (low + high) // 2
if lst[mid] == target:
return mid
elif lst[mid] < target:
low = mid + 1 # target is in right half
else:
high = mid - 1 # target is in left half
return -1

nums = [1, 3, 5, 7, 9, 11, 13]


print(binary_search(nums, 7)) # 3
print(binary_search(nums, 6)) # -1

# Python's built-in bisect module


import bisect
idx = bisect.bisect_left(nums, 7)
print(idx) # 3

Selection Sort — O(n²)


def selection_sort(lst):
n = len(lst)
for i in range(n):
min_idx = i
for j in range(i+1, n):
if lst[j] < lst[min_idx]:
min_idx = j
lst[i], lst[min_idx] = lst[min_idx], lst[i]

nums = [64, 25, 12, 22, 11]


selection_sort(nums)
print(nums) # [11, 12, 22, 25, 64]

— 50 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Insertion Sort — O(n²) worst, O(n) best


def insertion_sort(lst):
for i in range(1, len(lst)):
key = lst[i]
j = i - 1
while j >= 0 and lst[j] > key:
lst[j+1] = lst[j]
j -= 1
lst[j+1] = key

Merge Sort — O(n log n)


def merge_sort(lst):
if len(lst) <= 1:
return lst
mid = len(lst) // 2
left = merge_sort(lst[:mid])
right = merge_sort(lst[mid:])
return merge(left, right)

def merge(left, right):


result, i, j = [], 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
[Link](left[i]); i += 1
else:
[Link](right[j]); j += 1
[Link](left[i:])
[Link](right[j:])
return result

print(merge_sort([38, 27, 43, 3, 9, 82]))


# [3, 9, 27, 38, 43, 82]

— 51 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

Section 18 Recursion
base case · recursive case · call stack · memoization · classic problems

What is Recursion?
Recursion is when a function calls itself to solve a smaller version of the same problem. Every recursive
solution must have:

• Base case — the simplest input that can be solved directly (no further recursion).
• Recursive case — break the problem into a smaller sub-problem and call self.
• Progress toward the base case — each call must bring the problem closer to the base case.
Call Stack
Each function call pushes a frame onto the call stack. Recursive calls stack up until the base case is hit,
then they 'unwind'. Python's default stack limit is ~1000 frames ([Link]() can increase it).

Classic Example: Factorial


# n! = n * (n-1) * ... * 1, and 0! = 1
def factorial(n):
# Base case
if n == 0:
return 1
# Recursive case
return n * factorial(n - 1)

# Execution trace: factorial(4)


# = 4 * factorial(3)
# = 4 * (3 * factorial(2))
# = 4 * (3 * (2 * factorial(1)))
# = 4 * (3 * (2 * (1 * factorial(0))))
# = 4 * (3 * (2 * (1 * 1)))
# = 24

print(factorial(5)) # 120
print(factorial(0)) # 1

Fibonacci
# Naive recursion — O(2^n), very slow
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)

# Memoized — O(n)
from functools import lru_cache

— 52 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

@lru_cache(maxsize=None)
def fib_memo(n):
if n <= 1:
return n
return fib_memo(n-1) + fib_memo(n-2)

print([fib_memo(i) for i in range(10)])


# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Sum of a List
def recursive_sum(lst):
if len(lst) == 0:
return 0
return lst[0] + recursive_sum(lst[1:])

print(recursive_sum([1,2,3,4,5])) # 15

Power Function
def power(base, exp):
if exp == 0:
return 1
return base * power(base, exp - 1)

# Optimised: divide and conquer — O(log n)


def fast_power(base, exp):
if exp == 0:
return 1
if exp % 2 == 0:
half = fast_power(base, exp // 2)
return half * half
return base * fast_power(base, exp - 1)

print(fast_power(2, 10)) # 1024

Binary Search (Recursive)


def binary_search_rec(lst, target, low, high):
if low > high:
return -1
mid = (low + high) // 2
if lst[mid] == target:
return mid
elif lst[mid] < target:
return binary_search_rec(lst, target, mid+1, high)
else:
return binary_search_rec(lst, target, low, mid-1)

nums = [1, 3, 5, 7, 9, 11]

— 53 —
Comprehensive Python Guide Sections 1–18 · All Skill Levels

print(binary_search_rec(nums, 7, 0, len(nums)-1)) # 3

Tower of Hanoi
def hanoi(n, source, destination, auxiliary):
"""Move n disks from source to destination using auxiliary."""
if n == 1:
print(f"Move disk 1 from {source} to {destination}")
return
hanoi(n-1, source, auxiliary, destination)
print(f"Move disk {n} from {source} to {destination}")
hanoi(n-1, auxiliary, destination, source)

hanoi(3, "A", "C", "B")


# Requires 2^n - 1 moves for n disks

Recursion vs Iteration
Recursion often mirrors the mathematical definition; iteration is more
Readability explicit.

Iteration is generally faster (no function call overhead, no stack


Performance limit).

Deep recursion can hit Python's ~1000 frame limit; iteration does
Stack limit not.

Recursion shines for tree/graph traversal, divide & conquer, and


Best use backtracking.

@lru_cache or manual dict can make recursive solutions as fast as


Memoization iterative.

End of Guide
You have covered all 18 sections of this Comprehensive Python Programming Guide.
Happy coding! ■

— 54 —

You might also like