DAY 1: PYTHON FOUNDATIONS
VARIABLES & DATA TYPES
Variables store data values. Python is dynamically typed.
# Integer
age = 25
print(type(age)) #
# Float
price = 19.99
print(type(price)) #
# String
name = "Alice"
print(type(name)) #
# Boolean
is_active = True
print(type(is_active)) #
TIPS:
- Variable names should be descriptive (use snake_case)
- Python is case-sensitive:
myVar ≠ myvar
- Use
type() to check variable type
TYPE CASTING
Converting between data types.
# String to integer
num_str = "123"
num_int = int(num_str) # 123
# Integer to string
num = 456
num_as_str = str(num) # "456"
# Float to integer (truncates decimal)
float_num = 3.99
int_num = int(float_num) # 3 (not rounded!)
# Integer to float
int_num = 7
float_num = float(int_num) # 7.0
COMMON PITFALLS:
int("3.14") raises ValueError (use float first)
int("hello") raises ValueError
- Cast before operations:
int("5") + 10 works, "5" + 10 doesn't
INPUT & OUTPUT
Getting user input and displaying output.
# Basic print
print("Hello, World!")
print("Value is:", 42)
# Formatted output
name = "Bob"
age = 30
print(f"{name} is {age} years old") # f-string (modern)
print("{} is {} years old".format(name, age)) # .format()
print("%s is %d years old" % (name, age)) # % formatting
# User input
user_name = input("Enter your name: ")
print(f"Hello, {user_name}!")
# Input always returns string
age_input = input("Enter your age: ")
age = int(age_input) # Convert to int for calculations
DAY 2: OPERATORS & CONTROL FLOW
OPERATORS
| TYPE |
OPERATOR |
DESCRIPTION |
EXAMPLE |
| Arithmetic |
+ - * / // % ** |
Addition, subtraction, multiplication, division, floor division, modulus, exponent |
10 // 3 = 3 |
| Comparison |
== != > < >= <= |
Equal, not equal, greater than, less than, etc. |
5 != 3 → True |
| Logical |
and or not |
Logical AND, OR, NOT |
True and False → False |
| Assignment |
= += -= *= /= |
Assign and update values |
x += 5 (x = x + 5) |
| Membership |
in not in |
Check if value exists in sequence |
"a" in "apple" → True |
| Identity |
is is not |
Check if objects are the same |
a is b |
CONDITIONAL STATEMENTS
# Basic if-else
age = 18
if age >= 18:
print("You can vote!")
else:
print("Too young to vote")
# if-elif-else chain
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B" # This executes for score=85
elif score >= 70:
grade = "C"
else:
grade = "F"
print(f"Grade: {grade}")
# Nested conditions
temperature = 25
is_sunny = True
if temperature > 20:
if is_sunny:
print("Perfect beach weather!")
else:
print("Warm but cloudy")
else:
print("Too cold for beach")
# One-liner (ternary)
status = "adult" if age >= 18 else "minor"
TRUTHY & FALSY VALUES:
- Falsy:
False, 0, 0.0, "", [], {}, None
- Truthy: Everything else (including negative numbers!)
- Useful for checking if list is empty:
if items: (not if len(items) > 0:)
DAY 3: LOOPS & FLOW CONTROL
FOR LOOPS
# Basic for loop
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# With range()
for i in range(5): # 0 to 4
print(i)
for i in range(2, 8): # 2 to 7
print(i)
for i in range(0, 10, 2): # 0, 2, 4, 6, 8
print(i)
# Looping with index
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# Nested loops
for i in range(3):
for j in range(2):
print(f"({i}, {j})")
WHILE LOOPS
# Basic while loop
count = 0
while count < 5:
print(count)
count += 1 # Don't forget to increment!
# While with condition
password = ""
while password != "secret":
password = input("Enter password: ")
print("Access granted!")
# Infinite loop (with break)
while True:
user_input = input("Enter 'quit' to exit: ")
if user_input == "quit":
break
print(f"You entered: {user_input}")
LOOP CONTROL STATEMENTS
# break - exit loop immediately
for i in range(10):
if i == 5:
break
print(i) # Prints 0,1,2,3,4
# continue - skip to next iteration
for i in range(5):
if i == 2:
continue
print(i) # Prints 0,1,3,4
# pass - placeholder (does nothing)
for i in range(5):
if i == 3:
pass # TODO: Add implementation later
print(i)
# else with loops (executes if loop completes without break)
for i in range(3):
print(i)
else:
print("Loop completed!") # This runs
for i in range(3):
if i == 1:
break
print(i)
else:
print("Loop completed!") # This doesn't run
PATTERN PRINTING EXAMPLE:
# Print right triangle
for i in range(1, 6):
print("*" * i)
# Output:
# *
# **
# ***
# ****
# *****
# Print pyramid
n = 5
for i in range(1, n+1):
spaces = " " * (n - i)
stars = "*" * (2*i - 1)
print(spaces + stars)
DAY 4: STRINGS & STRING HANDLING
STRING BASICS
# Creation
s1 = "Hello"
s2 = 'World'
s3 = """Multi-line
string"""
s4 = str(123) # "123"
# Indexing
text = "Python"
first_char = text[0] # "P"
last_char = text[-1] # "n"
# text[0] = "J" # ERROR: Strings are immutable!
# Slicing [start:end:step]
text = "Python Programming"
print(text[0:6]) # "Python"
print(text[7:]) # "Programming"
print(text[:6]) # "Python"
print(text[::2]) # "Pto rgamn"
print(text[::-1]) # Reverse: "gnimmargorP nohtyP"
STRING METHODS
| METHOD |
DESCRIPTION |
EXAMPLE |
.upper() .lower() |
Case conversion |
"Hello".upper() → "HELLO" |
.strip() .lstrip() .rstrip() |
Remove whitespace |
" hi ".strip() → "hi" |
.split() .join() |
Split/join strings |
"a,b,c".split(",") → ["a","b","c"] |
.replace() |
Replace substring |
"hello".replace("l","x") → "hexxo" |
.find() .index() |
Find substring |
"hello".find("ll") → 2 |
.startswith() .endswith() |
Check prefix/suffix |
"file.txt".endswith(".txt") → True |
.isalpha() .isdigit() .isalnum() |
Character tests |
"abc".isalpha() → True |
STRING PROBLEMS
# Palindrome check
def is_palindrome(s):
s = s.lower().replace(" ", "")
return s == s[::-1]
print(is_palindrome("racecar")) # True
print(is_palindrome("hello")) # False
# Anagram check
def is_anagram(s1, s2):
return sorted(s1.lower()) == sorted(s2.lower())
print(is_anagram("listen", "silent")) # True
print(is_anagram("hello", "world")) # False
DAY 5: LISTS, TUPLES & SETS
LISTS
# Creation
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True]
empty = []
from_range = list(range(5)) # [0,1,2,3,4]
# Access & modification
numbers[0] = 10 # Lists are mutable
print(numbers[-1]) # Last element: 5
print(numbers[1:4]) # Slice: [2,3,4]
# Common methods
numbers.append(6) # Add to end
numbers.insert(0, 0) # Insert at index 0
numbers.extend([7, 8]) # Add multiple items
removed = numbers.pop() # Remove last item (8)
numbers.remove(3) # Remove first occurrence of 3
if 4 in numbers: # Check existence
print("Found 4")
# List comprehension (powerful!)
squares = [x**2 for x in range(10)]
even_squares = [x**2 for x in range(10) if x % 2 == 0]
TUPLES
# Creation (immutable)
point = (10, 20)
single = (5,) # Comma required for single element
coordinates = tuple([1, 2, 3])
# Access (same as lists)
x = point[0]
y = point[1]
# Unpacking
x, y = point # x=10, y=20
# Why use tuples?
# 1. Faster than lists
# 2. Safe (can't be modified accidentally)
# 3. Can be used as dictionary keys
# 4. Return multiple values from functions
# Returning multiple values
def get_stats(numbers):
return min(numbers), max(numbers), sum(numbers)/len(numbers)
min_val, max_val, avg = get_stats([1,2,3,4,5])
SETS
# Creation (unique, unordered)
unique_nums = {1, 2, 3, 3, 2} # {1, 2, 3}
empty_set = set() # {} creates empty dict!
# Set operations
A = {1, 2, 3, 4}
B = {3, 4, 5, 6}
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 difference: {1,2,5,6}
# Methods
A.add(5)
A.remove(2) # Error if not exists
A.discard(2) # No error if not exists
A.pop() # Remove random element
# Set comprehension
unique_chars = {char for char in "hello world" if char != " "}
DAY 6: DICTIONARIES & DATA STRUCTURES
DICTIONARY BASICS
# Creation
student = {"name": "Alice", "age": 20, "grade": "A"}
empty_dict = {}
another = dict(name="Bob", age=21)
# Access
print(student["name"]) # "Alice"
print(student.get("age")) # 20
print(student.get("height", "N/A")) # "N/A" (default if key missing)
# Modification
student["age"] = 21 # Update
student["height"] = 165 # Add new key
del student["grade"] # Remove key
# Check key existence
if "name" in student:
print("Name exists")
# Iteration
for key in student: # Or student.keys()
print(key, student[key])
for key, value in student.items():
print(f"{key}: {value}")
DICTIONARY METHODS
| METHOD |
DESCRIPTION |
EXAMPLE |
.keys() |
Get all keys |
list(student.keys()) |
.values() |
Get all values |
list(student.values()) |
.items() |
Get (key, value) pairs |
list(student.items()) |
.get(key, default) |
Safe access with default |
student.get("x", 0) |
.pop(key, default) |
Remove and return value |
age = student.pop("age") |
.update(other_dict) |
Merge dictionaries |
student.update({"city": "NYC"}) |
.setdefault(key, default) |
Get value or set default |
student.setdefault("score", 100) |
NESTED DICTIONARIES & JSON
# Nested dictionary
company = {
"name": "TechCorp",
"employees": {
"101": {"name": "Alice", "position": "Developer"},
"102": {"name": "Bob", "position": "Designer"}
},
"departments": ["IT", "HR", "Finance"]
}
# Access nested values
print(company["employees"]["101"]["name"]) # "Alice"
# JSON-like structure (common in web APIs)
import json
# Python dict to JSON string
person_dict = {"name": "John", "age": 30}
json_string = json.dumps(person_dict) # '{"name": "John", "age": 30}'
# JSON string to Python dict
parsed_dict = json.loads(json_string)
# Dictionary comprehension
squares = {x: x**2 for x in range(5)} # {0:0, 1:1, 2:4, 3:9, 4:16}
DAY 7: FUNCTIONS & FUNCTIONAL PROGRAMMING
FUNCTION BASICS
# Defining functions
def greet(name):
"""Return a greeting message.""" # Docstring
return f"Hello, {name}!"
# Calling functions
message = greet("Alice")
print(message) # "Hello, Alice!"
# Functions without return
def print_sum(a, b):
print(f"Sum: {a + b}") # Returns None
# Multiple return values
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([1, 5, 2, 8, 3])
FUNCTION ARGUMENTS
# Positional arguments
def power(base, exponent):
return base ** exponent
result = power(2, 3) # 8
# Keyword arguments
result = power(exponent=3, base=2) # Order doesn't matter
# Default parameters
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Alice")) # "Hello, Alice!"
print(greet("Bob", "Hi")) # "Hi, Bob!"
# Variable-length arguments
def sum_all(*args): # args is a tuple
return sum(args)
print(sum_all(1, 2, 3, 4)) # 10
def print_info(**kwargs): # kwargs is a dictionary
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25, city="NYC")
LAMBDA & FUNCTIONAL TOOLS
# Lambda functions (anonymous)
square = lambda x: x ** 2
print(square(5)) # 25
# Often used with map, filter, reduce
numbers = [1, 2, 3, 4, 5]
# map: apply function to all items
squares = list(map(lambda x: x**2, numbers)) # [1, 4, 9, 16, 25]
# filter: keep items where function returns True
evens = list(filter(lambda x: x % 2 == 0, numbers)) # [2, 4]
# reduce: apply function cumulatively
from functools import reduce
product = reduce(lambda x, y: x * y, numbers) # 120
# List comprehension alternative
squares = [x**2 for x in numbers] # Often more readable
evens = [x for x in numbers if x % 2 == 0]
RECURSION
# Factorial example
def factorial(n):
if n <= 1: # Base case
return 1
return n * factorial(n - 1) # Recursive case
print(factorial(5)) # 120
# Fibonacci sequence
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(6)) # 8
# Recursion tips:
# 1. Always have a base case
# 2. Ensure progress toward base case
# 3. Python has recursion limit (~1000)
DAY 8: MODULES, PACKAGES & FILE HANDLING
MODULES
# Importing modules
import math
print(math.sqrt(16)) # 4.0
# Import with alias
import datetime as dt
today = dt.date.today()
# Import specific functions
from random import randint, choice
number = randint(1, 10)
item = choice(["apple", "banana", "cherry"])
# Import everything (not recommended)
from os import * # Pollutes namespace
# Create your own module
# Save as mymodule.py:
"""
def greet(name):
return f"Hello, {name}!"
PI = 3.14159
"""
# Then import it:
# import mymodule
# print(mymodule.greet("Alice"))
USEFUL BUILT-IN MODULES
| MODULE |
PURPOSE |
COMMON FUNCTIONS |
math |
Mathematical operations |
sqrt(), ceil(), floor(), pi |
random |
Random number generation |
randint(), choice(), shuffle() |
datetime |
Date and time operations |
date.today(), datetime.now() |
os |
Operating system interface |
listdir(), getcwd(), path.join() |
sys |
System parameters |
argv, exit(), version |
json |
JSON encoding/decoding |
dumps(), loads() |
FILE HANDLING
# Reading files
with open("data.txt", "r") as file: # 'r' = read mode
content = file.read() # Read entire file
# OR: lines = file.readlines() # List of lines
# Writing files
with open("output.txt", "w") as file: # 'w' = write (overwrites)
file.write("Hello, World!\n")
file.write("Second line\n")
# Appending to files
with open("log.txt", "a") as file: # 'a' = append
file.write("New log entry\n")
# Reading line by line (memory efficient)
with open("large_file.txt", "r") as file:
for line in file:
print(line.strip()) # Process each line
# File modes:
# 'r' - read (default)
# 'w' - write (creates or truncates)
# 'a' - append (creates or appends)
# 'x' - exclusive creation (fails if exists)
# 'b' - binary mode ('rb', 'wb')
# '+' - read and write ('r+', 'w+')
DAY 9: EXCEPTION HANDLING & DEBUGGING
EXCEPTION HANDLING BASICS
# Basic try-except
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero!")
# Multiple exceptions
try:
num = int(input("Enter a number: "))
result = 10 / num
print(f"Result: {result}")
except ValueError:
print("Invalid input! Please enter a number.")
except ZeroDivisionError:
print("Cannot divide by zero!")
except Exception as e: # Catch any other exception
print(f"Unexpected error: {e}")
# else and finally
try:
file = open("data.txt", "r")
content = file.read()
except FileNotFoundError:
print("File not found!")
else:
print("File read successfully!")
print(content)
finally:
print("This always executes")
# Good for cleanup (closing files, connections)
COMMON EXCEPTIONS
| EXCEPTION |
CAUSE |
EXAMPLE |
ValueError |
Invalid value/conversion |
int("abc") |
TypeError |
Wrong type operation |
"5" + 3 |
IndexError |
Invalid sequence index |
list[10] (list has 3 items) |
KeyError |
Missing dictionary key |
dict["missing_key"] |
FileNotFoundError |
File doesn't exist |
open("nonexistent.txt") |
ZeroDivisionError |
Division by zero |
10 / 0 |
AttributeError |
Attribute doesn't exist |
"hello".non_existent() |
RAISING EXCEPTIONS
# Raising exceptions
def calculate_age(birth_year):
if birth_year < 1900 or birth_year > 2023:
raise ValueError("Invalid birth year")
return 2023 - birth_year
try:
age = calculate_age(1800)
except ValueError as e:
print(f"Error: {e}")
# Custom exceptions
class InvalidEmailError(Exception):
"""Exception raised for invalid email addresses."""
def __init__(self, email, message="Invalid email address"):
self.email = email
self.message = message
super().__init__(self.message)
def validate_email(email):
if "@" not in email:
raise InvalidEmailError(email)
return True
# Assertions (for debugging)
def calculate_average(numbers):
assert len(numbers) > 0, "List cannot be empty"
return sum(numbers) / len(numbers)
DAY 10: OBJECT-ORIENTED PROGRAMMING (OOP)
CLASSES & OBJECTS
# Basic class
class Dog:
# Class attribute (shared by all instances)
species = "Canis familiaris"
# Constructor (__init__ method)
def __init__(self, name, age):
# Instance attributes
self.name = name
self.age = age
# Instance method
def bark(self):
return f"{self.name} says woof!"
def get_info(self):
return f"{self.name} is {self.age} years old"
# Creating objects
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
print(dog1.bark()) # "Buddy says woof!"
print(dog2.get_info()) # "Max is 5 years old"
print(Dog.species) # "Canis familiaris" (class attribute)
INHERITANCE
# Base class
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement this method")
# Child class
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name) # Call parent constructor
self.color = color
# Override parent method
def speak(self):
return f"{self.name} says meow!"
def get_info(self):
return f"{self.name} is a {self.color} cat"
# Using inheritance
cat = Cat("Whiskers", "orange")
print(cat.speak()) # "Whiskers says meow!"
print(cat.get_info()) # "Whiskers is an orange cat"
# Multiple inheritance
class Pet:
def __init__(self, owner):
self.owner = owner
class DogPet(Dog, Pet):
def __init__(self, name, age, owner):
Dog.__init__(self, name, age)
Pet.__init__(self, owner)
ENCAPSULATION & SPECIAL METHODS
# Encapsulation with private attributes
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # Private attribute
# Getter method
def get_balance(self):
return self.__balance
# Setter method (with validation)
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
return True
return False
# Special methods (dunder methods)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self): # String representation
return f"Vector({self.x}, {self.y})"
def __repr__(self): # Official representation
return f"Vector({self.x}, {self.y})"
def __add__(self, other): # + operator
return Vector(self.x + other.x, self.y + other.y)
def __len__(self): # len() function
return 2
def __eq__(self, other): # == operator
return self.x == other.x and self.y == other.y
v1 = Vector(2, 3)
v2 = Vector(1, 1)
print(v1 + v2) # Vector(3, 4)
print(v1 == v2) # False
POLYMORPHISM & ABSTRACTION
# Polymorphism - same interface, different implementation
class Shape:
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
# Different objects, same method
shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
print(f"Area: {shape.area()}") # Polymorphic call
# Abstraction - hide complex implementation
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def connect(self):
pass
@abstractmethod
def query(self, sql):
pass
class MySQLDatabase(Database):
def connect(self):
print("Connecting to MySQL...")
def query(self, sql):
print(f"Executing MySQL query: {sql}")
# Can't instantiate abstract class
# db = Database() # Error!
db = MySQLDatabase() # OK