Complete Python Learning Roadmap
Professional Guide from Beginner to Expert
Table of Contents
- Introduction to Python
- Environment Setup & Tools
- Python Basics
- Data Types & Structures
- Control Flow
- Functions & Modules
- Object-Oriented Programming (OOP)
- File Handling & I/O
- Exception Handling
- Advanced Python Concepts
- Standard Library Deep Dive
- Working with Data
- Web Development
- Database Programming
- Testing & Debugging
- Best Practices & Design Patterns
- Real-World Projects
- Interview Questions & Answers
1. Introduction to Python
What is Python?
Python is a high-level, interpreted, general-purpose programming language created by Guido van Rossum in 1991.
Key Features:
- Easy to Learn: Simple, readable syntax
- Interpreted: No compilation needed
- Dynamically Typed: No need to declare variable types
- Multi-paradigm: Supports procedural, OOP, and functional programming
- Extensive Libraries: Rich ecosystem for virtually any task
- Cross-platform: Runs on Windows, Mac, Linux
Use Cases:
- Web Development (Django, Flask)
- Data Science & AI (NumPy, Pandas, TensorFlow)
- Automation & Scripting
- Desktop Applications
- Game Development
- DevOps & Cloud
Python Philosophy (The Zen of Python):
import this
Output:
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Readability counts.
2. Environment Setup & Tools
Installation
Windows
# Download from python.org
# During installation, check "Add Python to PATH"
# Verify installation
python --version
pip --version
Mac/Linux
# Mac (using Homebrew)
brew install python3
# Linux (Ubuntu/Debian)
sudo apt update
sudo apt install python3 python3-pip
# Verify
python3 --version
pip3 --version
Setting Up Development Environment
Option 1: VS Code (Recommended)
- Download from code.visualstudio.com
- Install Python extension
- Configure Python interpreter
- Enable linting and formatting
Option 2: PyCharm
- Professional IDE for Python
- Free Community Edition available
Option 3: Jupyter Notebook
pip install jupyter
jupyter notebook
Virtual Environments
# Create virtual environment
python -m venv myenv
# Activate (Windows)
myenv\Scripts\activate
# Activate (Mac/Linux)
source myenv/bin/activate
# Install packages
pip install package_name
# Freeze dependencies
pip freeze > requirements.txt
# Install from requirements
pip install -r requirements.txt
# Deactivate
deactivate
Your First Python Program
# hello_world.py
print("Hello, World!")
# Run the program
# python hello_world.py
3. Python Basics
Comments
# Single line comment
"""
Multi-line comment
or docstring
"""
'''
Another multi-line
comment
'''
Variables and Assignment
# Variable assignment (no declaration needed)
name = "John"
age = 30
height = 5.9
is_student = True
# Multiple assignment
x, y, z = 1, 2, 3
a = b = c = 0
# Swapping variables
x, y = y, x
# Variable naming rules
valid_name = "OK"
_private = "OK"
name2 = "OK"
# 2name = "Invalid" # Cannot start with number
# my-name = "Invalid" # No hyphens
Input and Output
# Output
print("Hello")
print("Name:", name, "Age:", age)
print(f"My name is {name} and I am {age} years old") # f-strings
print("Name: {} Age: {}".format(name, age)) # format method
# Input
user_name = input("Enter your name: ")
user_age = int(input("Enter your age: ")) # Convert to integer
user_height = float(input("Enter your height: ")) # Convert to float
print(f"Hello {user_name}, you are {user_age} years old")
Operators
# Arithmetic Operators
a, b = 10, 3
print(a + b) # Addition: 13
print(a - b) # Subtraction: 7
print(a * b) # Multiplication: 30
print(a / b) # Division: 3.333...
print(a // b) # Floor division: 3
print(a % b) # Modulus: 1
print(a ** b) # Exponentiation: 1000
# Comparison Operators
print(a == b) # Equal: False
print(a != b) # Not equal: True
print(a > b) # Greater than: True
print(a < b) # Less than: False
print(a >= b) # Greater or equal: True
print(a <= b) # Less or equal: False
# Logical Operators
x, y = True, False
print(x and y) # AND: False
print(x or y) # OR: True
print(not x) # NOT: False
# Assignment Operators
a = 10
a += 5 # a = a + 5
a -= 3 # a = a - 3
a *= 2 # a = a * 2
a /= 4 # a = a / 4
a //= 2 # a = a // 2
a %= 3 # a = a % 3
a **= 2 # a = a ** 2
# Identity Operators
x = [1, 2, 3]
y = [1, 2, 3]
z = x
print(x is z) # True (same object)
print(x is y) # False (different objects)
print(x == y) # True (same values)
print(x is not y) # True
# Membership Operators
list_items = [1, 2, 3, 4, 5]
print(3 in list_items) # True
print(10 not in list_items) # True
# Bitwise Operators
a, b = 5, 3 # 101, 011 in binary
print(a & b) # AND: 1 (001)
print(a | b) # OR: 7 (111)
print(a ^ b) # XOR: 6 (110)
print(~a) # NOT: -6
print(a << 1) # Left shift: 10 (1010)
print(a >> 1) # Right shift: 2 (010)
4. Data Types & Structures
Basic Data Types
# Integer
age = 25
big_number = 1_000_000 # Underscores for readability
# Float
price = 19.99
scientific = 1.5e-4 # 0.00015
# String
name = "John"
message = 'Hello'
multiline = """This is
a multiline
string"""
# Boolean
is_active = True
is_deleted = False
# None (null equivalent)
value = None
# Type checking
print(type(age)) # <class 'int'>
print(type(price)) # <class 'float'>
print(type(name)) # <class 'str'>
print(isinstance(age, int)) # True
# Type conversion
num_str = "123"
num_int = int(num_str)
num_float = float(num_str)
str_num = str(123)
bool_val = bool(1) # True
Strings
# String creation
text = "Hello, World!"
single = 'Single quotes'
multiline = """Multiple
lines"""
# String indexing and slicing
text = "Python"
print(text[0]) # 'P'
print(text[-1]) # 'n'
print(text[0:3]) # 'Pyt'
print(text[:3]) # 'Pyt'
print(text[3:]) # 'hon'
print(text[::2]) # 'Pto' (every 2nd character)
print(text[::-1]) # 'nohtyP' (reverse)
# String methods
text = " Hello, World! "
print(text.lower()) # ' hello, world! '
print(text.upper()) # ' HELLO, WORLD! '
print(text.strip()) # 'Hello, World!'
print(text.replace("World", "Python")) # ' Hello, Python! '
print(text.split(",")) # [' Hello', ' World! ']
print(text.startswith(" H")) # True
print(text.endswith("! ")) # True
print(text.find("World")) # 9
print(text.count("l")) # 3
# String formatting
name = "Alice"
age = 30
# f-strings (Python 3.6+)
print(f"Name: {name}, Age: {age}")
print(f"{name.upper()}")
print(f"Result: {10 + 20}")
print(f"{name:>10}") # Right align
print(f"{age:05d}") # Zero padding: 00030
# format() method
print("Name: {}, Age: {}".format(name, age))
print("Name: {0}, Age: {1}".format(name, age))
print("Name: {n}, Age: {a}".format(n=name, a=age))
# % operator (old style)
print("Name: %s, Age: %d" % (name, age))
# String concatenation
greeting = "Hello" + " " + "World"
repeated = "Ha" * 3 # 'HaHaHa'
# Raw strings (ignore escape characters)
path = r"C:\Users\name\folder"
Lists
# List creation
numbers = [1, 2, 3, 4, 5]
mixed = [1, "two", 3.0, True, None]
nested = [[1, 2], [3, 4], [5, 6]]
empty = []
# List indexing and slicing
numbers = [0, 1, 2, 3, 4, 5]
print(numbers[0]) # 0
print(numbers[-1]) # 5
print(numbers[1:4]) # [1, 2, 3]
print(numbers[:3]) # [0, 1, 2]
print(numbers[3:]) # [3, 4, 5]
print(numbers[::2]) # [0, 2, 4]
print(numbers[::-1]) # [5, 4, 3, 2, 1, 0]
# List methods
fruits = ["apple", "banana", "cherry"]
# Adding elements
fruits.append("date") # Add to end
fruits.insert(1, "blueberry") # Insert at index
fruits.extend(["elderberry", "fig"]) # Add multiple
# Removing elements
fruits.remove("banana") # Remove first occurrence
popped = fruits.pop() # Remove and return last
popped = fruits.pop(0) # Remove and return at index
del fruits[0] # Delete by index
fruits.clear() # Remove all elements
# Other methods
fruits = ["apple", "banana", "cherry", "banana"]
print(fruits.index("banana")) # 1 (first occurrence)
print(fruits.count("banana")) # 2
fruits.sort() # Sort in place
fruits.reverse() # Reverse in place
copy = fruits.copy() # Create shallow copy
# List operations
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = list1 + list2 # [1, 2, 3, 4, 5, 6]
repeated = list1 * 3 # [1, 2, 3, 1, 2, 3, 1, 2, 3]
print(2 in list1) # True
print(len(list1)) # 3
# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
matrix = [[i*j for j in range(3)] for i in range(3)]
# Advanced list comprehension
numbers = [1, 2, 3, 4, 5]
result = [x*2 if x % 2 == 0 else x for x in numbers] # [1, 4, 3, 8, 5]
Tuples
# Tuple creation (immutable)
point = (10, 20)
single = (1,) # Note the comma
mixed = (1, "two", 3.0)
nested = ((1, 2), (3, 4))
# Tuple unpacking
x, y = point
a, b, c = mixed
# Tuple methods
numbers = (1, 2, 3, 2, 4, 2)
print(numbers.count(2)) # 3
print(numbers.index(3)) # 2
# Tuples are immutable
# numbers[0] = 10 # TypeError
# Use cases for tuples
# 1. Return multiple values from function
def get_coordinates():
return (10, 20)
# 2. Dictionary keys (tuples are hashable)
locations = {
(0, 0): "origin",
(1, 1): "point A"
}
# 3. Data integrity (cannot be modified)
config = ("localhost", 8080, "admin")
Sets
# Set creation (unordered, unique elements)
numbers = {1, 2, 3, 4, 5}
mixed = {1, "two", 3.0}
empty = set() # {} creates empty dict
# Set operations
set1 = {1, 2, 3, 4, 5}
set2 = {4, 5, 6, 7, 8}
# Union
print(set1 | set2) # {1, 2, 3, 4, 5, 6, 7, 8}
print(set1.union(set2))
# Intersection
print(set1 & set2) # {4, 5}
print(set1.intersection(set2))
# Difference
print(set1 - set2) # {1, 2, 3}
print(set1.difference(set2))
# Symmetric difference
print(set1 ^ set2) # {1, 2, 3, 6, 7, 8}
print(set1.symmetric_difference(set2))
# Set methods
numbers = {1, 2, 3}
numbers.add(4) # Add element
numbers.update([5, 6, 7]) # Add multiple
numbers.remove(1) # Remove (raises error if not found)
numbers.discard(10) # Remove (no error if not found)
popped = numbers.pop() # Remove and return arbitrary
numbers.clear() # Remove all
# Set comprehension
squares = {x**2 for x in range(10)}
# Membership testing (very fast)
large_set = set(range(1000000))
print(999999 in large_set) # O(1) time complexity
Dictionaries
# Dictionary creation (key-value pairs)
person = {
"name": "John",
"age": 30,
"city": "New York"
}
# Alternative creation
person = dict(name="John", age=30, city="New York")
# Accessing values
print(person["name"]) # "John"
print(person.get("age")) # 30
print(person.get("email", "N/A")) # "N/A" (default value)
# Modifying dictionary
person["age"] = 31 # Update
person["email"] = "john@email.com" # Add new
del person["city"] # Delete
# Dictionary methods
person = {"name": "John", "age": 30, "city": "New York"}
print(person.keys()) # dict_keys(['name', 'age', 'city'])
print(person.values()) # dict_values(['John', 30, 'New York'])
print(person.items()) # dict_items([('name', 'John'), ...])
# Iterating
for key in person:
print(key, person[key])
for key, value in person.items():
print(f"{key}: {value}")
# Dictionary operations
person.update({"age": 31, "country": "USA"}) # Update/add multiple
email = person.pop("email", None) # Remove and return
person.clear() # Remove all
# Dictionary comprehension
squares = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# Nested dictionaries
users = {
"user1": {"name": "Alice", "age": 25},
"user2": {"name": "Bob", "age": 30}
}
print(users["user1"]["name"]) # "Alice"
# Default dictionaries
from collections import defaultdict
word_count = defaultdict(int)
text = "hello world hello"
for word in text.split():
word_count[word] += 1
print(dict(word_count)) # {'hello': 2, 'world': 1}
5. Control Flow
If-Elif-Else Statements
# Basic if statement
age = 18
if age >= 18:
print("You are an adult")
# If-else
age = 15
if age >= 18:
print("You are an adult")
else:
print("You are a minor")
# If-elif-else
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"Your grade is: {grade}")
# Nested if
age = 20
has_license = True
if age >= 18:
if has_license:
print("You can drive")
else:
print("You need a license")
else:
print("You are too young to drive")
# Ternary operator
age = 20
status = "adult" if age >= 18 else "minor"
# Multiple conditions
x = 15
if x > 10 and x < 20:
print("x is between 10 and 20")
if 10 < x < 20: # Pythonic way
print("x is between 10 and 20")
# Checking multiple values
fruit = "apple"
if fruit in ["apple", "banana", "orange"]:
print("Valid fruit")
# Truthy and Falsy values
# Falsy: False, None, 0, 0.0, "", [], {}, ()
# Everything else is Truthy
name = ""
if name:
print(f"Hello, {name}")
else:
print("Name is empty")
# Short-circuit evaluation
def expensive_operation():
print("This is expensive!")
return True
x = 0
if x != 0 and expensive_operation(): # expensive_operation() not called
print("Both conditions met")
For Loops
# Basic for loop
for i in range(5):
print(i) # 0, 1, 2, 3, 4
# Range with start, stop, step
for i in range(1, 10, 2):
print(i) # 1, 3, 5, 7, 9
# Iterating over list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# Enumerate (get index and value)
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
for index, fruit in enumerate(fruits, start=1):
print(f"{index}: {fruit}")
# Iterating over dictionary
person = {"name": "John", "age": 30, "city": "New York"}
for key in person:
print(key)
for value in person.values():
print(value)
for key, value in person.items():
print(f"{key}: {value}")
# Iterating over string
for char in "Python":
print(char)
# Nested loops
for i in range(3):
for j in range(3):
print(f"({i}, {j})", end=" ")
print()
# Loop with else (executes if loop completes normally)
for i in range(5):
if i == 10:
break
else:
print("Loop completed normally")
# Zip (iterate over multiple sequences)
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
# Practical examples
# Sum of numbers
total = 0
for i in range(1, 101):
total += i
print(f"Sum: {total}")
# Multiplication table
n = 5
for i in range(1, 11):
print(f"{n} x {i} = {n*i}")
# Finding maximum
numbers = [3, 7, 2, 9, 4]
max_num = numbers[0]
for num in numbers:
if num > max_num:
max_num = num
print(f"Maximum: {max_num}")
While Loops
# Basic while loop
count = 0
while count < 5:
print(count)
count += 1
# While with condition
password = ""
while password != "secret":
password = input("Enter password: ")
print("Access granted!")
# While with break
count = 0
while True:
print(count)
count += 1
if count >= 5:
break
# While with continue
count = 0
while count < 10:
count += 1
if count % 2 == 0:
continue
print(count) # Only odd numbers
# While with else
count = 0
while count < 5:
print(count)
count += 1
else:
print("Loop completed")
# Infinite loop with break condition
while True:
user_input = input("Enter 'quit' to exit: ")
if user_input.lower() == 'quit':
break
print(f"You entered: {user_input}")
# Practical examples
# Factorial
n = 5
factorial = 1
i = 1
while i <= n:
factorial *= i
i += 1
print(f"Factorial of {n}: {factorial}")
# Guessing game
import random
target = random.randint(1, 100)
attempts = 0
while True:
guess = int(input("Guess the number (1-100): "))
attempts += 1
if guess == target:
print(f"Correct! You got it in {attempts} attempts")
break
elif guess < target:
print("Too low!")
else:
print("Too high!")
Loop Control Statements
# break - exit loop immediately
for i in range(10):
if i == 5:
break
print(i) # 0, 1, 2, 3, 4
# continue - skip rest of current iteration
for i in range(10):
if i % 2 == 0:
continue
print(i) # 1, 3, 5, 7, 9
# pass - do nothing (placeholder)
for i in range(5):
if i == 2:
pass # Will implement later
else:
print(i)
# Combining break and continue
numbers = [1, 2, -1, 4, 5, -2, 7]
for num in numbers:
if num < 0:
break # Stop at first negative
if num % 2 == 0:
continue # Skip even numbers
print(num) # Only odd positive numbers
6. Functions & Modules
Defining Functions
# Basic function
def greet():
print("Hello, World!")
greet() # Call function
# Function with parameters
def greet_person(name):
print(f"Hello, {name}!")
greet_person("Alice")
# Function with multiple parameters
def add(a, b):
return a + b
result = add(5, 3)
print(result) # 8
# Function with default parameters
def greet_with_title(name, title="Mr."):
print(f"Hello, {title} {name}")
greet_with_title("Smith") # Hello, Mr. Smith
greet_with_title("Smith", "Dr.") # Hello, Dr. Smith
# Keyword arguments
def describe_person(name, age, city):
print(f"{name} is {age} years old and lives in {city}")
describe_person(age=30, city="NYC", name="John")
# *args - variable number of arguments
def sum_all(*numbers):
total = 0
for num in numbers:
total += num
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
# **kwargs - variable keyword arguments
def print_info(**info):
for key, value in info.items():
print(f"{key}: {value}")
print_info(name="John", age=30, city="NYC")
# Combining args and kwargs
def complex_function(a, b, *args, **kwargs):
print(f"a: {a}, b: {b}")
print(f"args: {args}")
print(f"kwargs: {kwargs}")
complex_function(1, 2, 3, 4, 5, x=10, y=20)
# Return multiple values
def get_stats(numbers):
return min(numbers), max(numbers), sum(numbers) / len(numbers)
minimum, maximum, average = get_stats([1, 2, 3, 4, 5])
# Return dictionary
def get_person():
return {"name": "John", "age": 30}
person = get_person()
Function Scope
# Global vs Local scope
global_var = "I'm global"
def my_function():
local_var = "I'm local"
print(global_var) # Can access global
print(local_var)
my_function()
# print(local_var) # Error: local_var not defined
# Global keyword
counter = 0
def increment():
global counter
counter += 1
increment()
print(counter) # 1
# Nonlocal keyword (for nested functions)
def outer():
x = "outer"
def inner():
nonlocal x
x = "inner"
inner()
print(x) # "inner"
outer()
# LEGB Rule (Local, Enclosing, Global, Built-in)
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # "local"
inner()
print(x) # "enclosing"
outer()
print(x) # "global"
Lambda Functions
# Basic lambda
square = lambda x: x ** 2
print(square(5)) # 25
# Lambda with multiple parameters
add = lambda a, b: a + b
print(add(3, 5)) # 8
# Lambda in map()
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# Lambda in filter()
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]
# Lambda in sorted()
students = [
{"name": "Alice", "grade": 85},
{"name": "Bob", "grade": 92},
{"name": "Charlie", "grade": 78}
]
sorted_students = sorted(students, key=lambda x: x["grade"], reverse=True)
# Lambda with conditional
max_of_two = lambda a, b: a if a > b else b
print(max_of_two(10, 20)) # 20
Built-in Functions
# Numeric functions
print(abs(-5)) # 5
print(pow(2, 3)) # 8
print(round(3.14159, 2)) # 3.14
print(max(1, 5, 3)) # 5
print(min(1, 5, 3)) # 1
print(sum([1, 2, 3, 4])) # 10
# Type conversion
print(int("123")) # 123
print(float("3.14")) # 3.14
print(str(123)) # "123"
print(list("hello")) # ['h', 'e', 'l', 'l', 'o']
print(tuple([1, 2, 3])) # (1, 2, 3)
print(set([1, 2, 2, 3])) # {1, 2, 3}
# Sequence functions
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(len(numbers)) # 8
print(sorted(numbers)) # [1, 1, 2, 3, 4, 5, 6, 9]
print(reversed(numbers)) # reverse iterator
# Map, filter, reduce
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
from functools import reduce
product = reduce(lambda x, y: x * y, numbers) # 120
# Zip
names = ["Alice", "Bob"]
ages = [25, 30]
combined = list(zip(names, ages))
# [('Alice', 25), ('Bob', 30)]
# Enumerate
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
# Any and All
numbers = [2, 4, 6, 8]
print(all(x % 2 == 0 for x in numbers)) # True
print(any(x > 5 for x in numbers)) # True
# Range
print(list(range(5))) # [0, 1, 2, 3, 4]
print(list(range(1, 6))) # [1, 2, 3, 4, 5]
print(list(range(0, 10, 2))) # [0, 2, 4, 6, 8]
Modules and Packages
# Importing modules
import math
print(math.pi) # 3.141592653589793
print(math.sqrt(16)) # 4.0
# Import specific functions
from math import pi, sqrt
print(pi)
print(sqrt(16))
# Import with alias
import datetime as dt
now = dt.datetime.now()
from pandas as pd
import numpy as np
# Import all (not recommended)
from math import *
# Creating your own module
# mymodule.py
def greet(name):
return f"Hello, {name}!"
PI = 3.14159
# Using your module
# import mymodule
# print(mymodule.greet("Alice"))
# print(mymodule.PI)
# Package structure
"""
mypackage/
__init__.py
module1.py
module2.py
subpackage/
__init__.py
module3.py
"""
# Using packages
# from mypackage import module1
# from mypackage.subpackage import module3
# Useful standard library modules
import os # Operating system interface
import sys # System-specific parameters
import json # JSON encoding/decoding
import random # Random number generation
import datetime # Date and time
import re # Regular expressions
import collections # Specialized container datatypes
import itertools # Iterator functions
import functools # Higher-order functions
7. Object-Oriented Programming (OOP)
Classes and Objects
# Defining a class
class Person:
# Class variable (shared by all instances)
species = "Homo sapiens"
# Constructor
def __init__(self, name, age):
# Instance variables
self.name = name
self.age = age
# Instance method
def introduce(self):
return f"Hi, I'm {self.name} and I'm {self.age} years old"
# Method with parameters
def have_birthday(self):
self.age += 1
return f"Happy birthday! Now {self.age} years old"
# Creating objects
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
# Accessing attributes
print(person1.name) # "Alice"
print(person1.age) # 25
# Calling methods
print(person1.introduce())
person1.have_birthday()
# Accessing class variables
print(Person.species)
print(person1.species)
Encapsulation
class BankAccount:
def __init__(self, account_number, balance):
self.account_number = account_number # Public
self._balance = balance # Protected (convention)
self.__pin = "1234" # Private (name mangling)
# Getter
def get_balance(self):
return self._balance
# Setter
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
# Private method
def __validate_pin(self, pin):
return pin == self.__pin
account = BankAccount("123456", 1000)
account.deposit(500)
print(account.get_balance()) # 1500
# Cannot access private directly
# print(account.__pin) # AttributeError
# But can access with name mangling (not recommended)
# print(account._BankAccount__pin)
# Property decorators
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if isinstance(value, str) and value:
self._name = value
else:
raise ValueError("Name must be a non-empty string")
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if isinstance(value, int) and 0 <= value <= 150:
self._age = value
else:
raise ValueError("Age must be between 0 and 150")
person = Person("Alice", 25)
person.name = "Alicia" # Using setter
print(person.name) # Using getter
Inheritance
# Single inheritance
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def make_sound(self):
return "Some generic sound"
def info(self):
return f"{self.name} is a {self.species}"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, "Dog") # Call parent constructor
self.breed = breed
# Method overriding
def make_sound(self):
return "Woof!"
# Additional method
def fetch(self):
return f"{self.name} is fetching the ball"
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name, "Cat")
self.color = color
def make_sound(self):
return "Meow!"
# Using inherited classes
dog = Dog("Buddy", "Golden Retriever")
print(dog.info()) # Inherited method
print(dog.make_sound()) # Overridden method
print(dog.fetch()) # New method
cat = Cat("Whiskers", "Orange")
print(cat.make_sound())
# Multiple inheritance
class Flyable:
def fly(self):
return "Flying in the air"
class Swimmable:
def swim(self):
return "Swimming in water"
class Duck(Animal, Flyable, Swimmable):
def __init__(self, name):
super().__init__(name, "Duck")
def make_sound(self):
return "Quack!"
duck = Duck("Donald")
print(duck.make_sound())
print(duck.fly())
print(duck.swim())
# Method Resolution Order (MRO)
print(Duck.__mro__)
Polymorphism
# Method overriding (runtime polymorphism)
class Shape:
def area(self):
pass
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
# Polymorphism in action
shapes = [
Rectangle(5, 10),
Circle(7),
Rectangle(3, 4)
]
for shape in shapes:
print(f"Area: {shape.area()}")
print(f"Perimeter: {shape.perimeter()}")
# Duck typing
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Robot:
def speak(self):
return "Beep boop!"
def animal_sound(animal):
print(animal.speak())
# Works with any object that has a speak() method
animal_sound(Dog())
animal_sound(Cat())
animal_sound(Robot())
Special Methods (Magic Methods)
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
# String representation
def __str__(self):
return f"{self.title} by {self.author}"
def __repr__(self):
return f"Book('{self.title}', '{self.author}', {self.pages})"
# Length
def __len__(self):
return self.pages
# Comparison operators
def __eq__(self, other):
return self.pages == other.pages
def __lt__(self, other):
return self.pages < other.pages
def __gt__(self, other):
return self.pages > other.pages
# Addition
def __add__(self, other):
return self.pages + other.pages
# Indexing
def __getitem__(self, index):
return self.title[index]
book1 = Book("Python Crash Course", "Eric Matthes", 544)
book2 = Book("Automate the Boring Stuff", "Al Sweigart", 592)
print(str(book1)) # Uses __str__
print(repr(book1)) # Uses __repr__
print(len(book1)) # Uses __len__
print(book1 == book2) # Uses __eq__
print(book1 < book2) # Uses __lt__
print(book1 + book2) # Uses __add__
print(book1[0]) # Uses __getitem__
# Context manager
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
# Usage
with FileManager('test.txt', 'w') as f:
f.write('Hello, World!')
Class Methods and Static Methods
class MyClass:
class_variable = 0
def __init__(self, value):
self.value = value
# Instance method
def instance_method(self):
return f"Instance method called with value: {self.value}"
# Class method
@classmethod
def class_method(cls):
cls.class_variable += 1
return f"Class method called. Class variable: {cls.class_variable}"
# Static method
@staticmethod
def static_method(x, y):
return x + y
# Alternative constructor (factory method)
@classmethod
def from_string(cls, string_value):
value = int(string_value)
return cls(value)
# Usage
obj = MyClass(10)
print(obj.instance_method())
print(MyClass.class_method())
print(MyClass.class_method())
print(MyClass.static_method(5, 3))
# Using factory method
obj2 = MyClass.from_string("20")
print(obj2.value)
Abstract Classes
from abc import ABC, abstractmethod
class Vehicle(ABC):
def __init__(self, brand, model):
self.brand = brand
self.model = model
@abstractmethod
def start_engine(self):
pass
@abstractmethod
def stop_engine(self):
pass
def info(self):
return f"{self.brand} {self.model}"
class Car(Vehicle):
def start_engine(self):
return "Car engine started with key"
def stop_engine(self):
return "Car engine stopped"
class Motorcycle(Vehicle):
def start_engine(self):
return "Motorcycle engine started with button"
def stop_engine(self):
return "Motorcycle engine stopped"
# Cannot instantiate abstract class
# vehicle = Vehicle("Generic", "Model") # TypeError
car = Car("Toyota", "Camry")
print(car.info())
print(car.start_engine())
8. File Handling & I/O
Reading Files
# Method 1: Using open() and close()
file = open('example.txt', 'r')
content = file.read()
print(content)
file.close()
# Method 2: Using with statement (recommended)
with open('example.txt', 'r') as file:
content = file.read()
print(content)
# File automatically closed
# Read line by line
with open('example.txt', 'r') as file:
for line in file:
print(line.strip())
# Read all lines into list
with open('example.txt', 'r') as file:
lines = file.readlines()
print(lines)
# Read specific number of characters
with open('example.txt', 'r') as file:
chunk = file.read(100) # Read first 100 characters
# Read one line at a time
with open('example.txt', 'r') as file:
line1 = file.readline()
line2 = file.readline()
Writing Files
# Write (overwrites existing content)
with open('output.txt', 'w') as file:
file.write("Hello, World!\n")
file.write("This is a new line\n")
# Write multiple lines
lines = ["Line 1\n", "Line 2\n", "Line 3\n"]
with open('output.txt', 'w') as file:
file.writelines(lines)
# Append (adds to existing content)
with open('output.txt', 'a') as file:
file.write("This is appended\n")
# Write and read
with open('data.txt', 'w+') as file:
file.write("Some data")
file.seek(0) # Move to beginning
content = file.read()
print(content)
File Modes
"""
'r' - Read (default)
'w' - Write (overwrites)
'a' - Append
'x' - Exclusive creation (fails if file exists)
'b' - Binary mode
't' - Text mode (default)
'+' - Read and write
Examples:
'rb' - Read binary
'wb' - Write binary
'r+' - Read and write
"""
# Binary files
with open('image.jpg', 'rb') as file:
data = file.read()
with open('copy.jpg', 'wb') as file:
file.write(data)
Working with CSV Files
import csv
# Writing CSV
data = [
['Name', 'Age', 'City'],
['Alice', 25, 'New York'],
['Bob', 30, 'San Francisco'],
['Charlie', 35, 'Chicago']
]
with open('people.csv', 'w', newline='') as file:
writer = csv.writer(file)
writer.writerows(data)
# Reading CSV
with open('people.csv', 'r') as file:
reader = csv.reader(file)
for row in reader:
print(row)
# Using DictWriter
with open('people.csv', 'w', newline='') as file:
fieldnames = ['Name', 'Age', 'City']
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({'Name': 'Alice', 'Age': 25, 'City': 'New York'})
writer.writerow({'Name': 'Bob', 'Age': 30, 'City': 'San Francisco'})
# Using DictReader
with open('people.csv', 'r') as file:
reader = csv.DictReader(file)
for row in reader:
print(f"{row['Name']} is {row['Age']} years old")
Working with JSON
import json
# Python to JSON
data = {
"name": "John",
"age": 30,
"city": "New York",
"hobbies": ["reading", "gaming"],
"married": False
}
# Write JSON to file
with open('data.json', 'w') as file:
json.dump(data, file, indent=4)
# Convert to JSON string
json_string = json.dumps(data, indent=4)
print(json_string)
# JSON to Python
with open('data.json', 'r') as file:
loaded_data = json.load(file)
print(loaded_data)
# Parse JSON string
json_string = '{"name": "Alice", "age": 25}'
parsed = json.loads(json_string)
print(parsed['name'])
# Pretty printing
print(json.dumps(data, indent=4, sort_keys=True))
Working with Path and Directories
import os
from pathlib import Path
# Current working directory
print(os.getcwd())
# Change directory
os.chdir('/path/to/directory')
# List files in directory
files = os.listdir('.')
print(files)
# Check if path exists
if os.path.exists('myfile.txt'):
print("File exists")
# Check if it's a file or directory
print(os.path.isfile('myfile.txt'))
print(os.path.isdir('mydir'))
# Create directory
os.mkdir('new_directory')
os.makedirs('parent/child/grandchild') # Create nested directories
# Remove directory
os.rmdir('directory') # Empty directory only
import shutil
shutil.rmtree('directory') # Directory with contents
# File operations
os.rename('old.txt', 'new.txt')
os.remove('file.txt')
shutil.copy('source.txt', 'dest.txt')
shutil.move('source.txt', 'destination/')
# Path manipulation
file_path = '/home/user/documents/file.txt'
print(os.path.basename(file_path)) # 'file.txt'
print(os.path.dirname(file_path)) # '/home/user/documents'
print(os.path.split(file_path)) # ('/home/user/documents', 'file.txt')
print(os.path.splitext(file_path)) # ('/home/user/documents/file', '.txt')
# Join paths
path = os.path.join('folder', 'subfolder', 'file.txt')
# Using pathlib (modern approach)
p = Path('documents/report.txt')
print(p.exists())
print(p.is_file())
print(p.parent)
print(p.name)
print(p.stem) # filename without extension
print(p.suffix) # extension
# Create file
p = Path('new_file.txt')
p.touch()
# Write to file
p.write_text('Hello, World!')
# Read from file
content = p.read_text()
# Iterate over directory
for file in Path('.').iterdir():
if file.is_file():
print(file.name)
# Glob patterns
for file in Path('.').glob('*.txt'):
print(file)
for file in Path('.').rglob('*.py'): # Recursive
print(file)
9. Exception Handling
Try-Except Blocks
# Basic exception handling
try:
number = int(input("Enter a number: "))
result = 10 / number
print(f"Result: {result}")
except:
print("An error occurred")
# Specific exceptions
try:
number = int(input("Enter a number: "))
result = 10 / number
except ValueError:
print("Invalid input! Please enter a number")
except ZeroDivisionError:
print("Cannot divide by zero!")
# Multiple exceptions in one except
try:
# some code
pass
except (ValueError, TypeError, KeyError) as e:
print(f"Error occurred: {e}")
# Generic exception with details
try:
# some code
pass
except Exception as e:
print(f"An error occurred: {type(e).__name__}: {e}")
# Else clause (executes if no exception)
try:
number = int(input("Enter a number: "))
result = 10 / number
except ValueError:
print("Invalid input!")
except ZeroDivisionError:
print("Cannot divide by zero!")
else:
print(f"Result: {result}")
# Finally clause (always executes)
try:
file = open('data.txt', 'r')
content = file.read()
except FileNotFoundError:
print("File not found!")
finally:
file.close() # Always closes file
print("Cleanup completed")
# Nested try-except
try:
try:
number = int(input("Enter a number: "))
except ValueError:
print("Invalid input!")
raise # Re-raise the exception
result = 10 / number
except ZeroDivisionError:
print("Cannot divide by zero!")
Raising Exceptions
# Raise built-in exception
def check_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
if age < 18:
raise Exception("Must be 18 or older")
return "Access granted"
try:
check_age(-5)
except ValueError as e:
print(f"ValueError: {e}")
# Raise with custom message
def divide(a, b):
if b == 0:
raise ZeroDivisionError("Division by zero is not allowed!")
return a / b
# Re-raising exceptions
try:
# some code
pass
except ValueError:
print("Handling error...")
raise # Re-raise the same exception
Custom Exceptions
# Creating custom exception
class InsufficientFundsError(Exception):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"Insufficient funds. Balance: {balance}, Required: {amount}")
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
return self.balance
# Using custom exception
account = BankAccount(1000)
try:
account.withdraw(1500)
except InsufficientFundsError as e:
print(f"Error: {e}")
print(f"Your balance: ${e.balance}")
print(f"Amount requested: ${e.amount}")
# Exception hierarchy
class ValidationError(Exception):
pass
class EmailValidationError(ValidationError):
pass
class PasswordValidationError(ValidationError):
pass
def validate_email(email):
if '@' not in email:
raise EmailValidationError("Invalid email format")
def validate_password(password):
if len(password) < 8:
raise PasswordValidationError("Password must be at least 8 characters")
try:
validate_email("invalid-email")
except ValidationError as e:
print(f"Validation error: {e}")
Common Built-in Exceptions
"""
Exception - Base class for all exceptions
AttributeError - Attribute not found
IOError - I/O operation failed
ImportError - Import failed
IndexError - Index out of range
KeyError - Key not found in dictionary
KeyboardInterrupt - User interrupted execution (Ctrl+C)
MemoryError - Out of memory
NameError - Variable not found
OSError - Operating system error
RuntimeError - Generic runtime error
StopIteration - Iterator has no more items
SyntaxError - Syntax error in code
TypeError - Operation on incompatible types
ValueError - Invalid value
ZeroDivisionError - Division by zero
FileNotFoundError - File not found
PermissionError - Permission denied
"""
# Examples
try:
my_list = [1, 2, 3]
print(my_list[10])
except IndexError:
print("List index out of range")
try:
my_dict = {'a': 1, 'b': 2}
print(my_dict['c'])
except KeyError:
print("Key not found in dictionary")
try:
result = "hello" + 5
except TypeError:
print("Cannot concatenate string and integer")
Assertions
# Assertions for debugging
def calculate_average(numbers):
assert len(numbers) > 0, "List cannot be empty"
return sum(numbers) / len(numbers)
# This will raise AssertionError
try:
average = calculate_average([])
except AssertionError as e:
print(f"Assertion failed: {e}")
# Assertions are disabled when Python runs in optimized mode
# python -O script.py
# Use assertions for:
# - Debugging
# - Checking invariants
# - Validating developer assumptions
# Don't use assertions for:
# - Data validation (use exceptions instead)
# - Production error handling
10. Advanced Python Concepts
Decorators
# Function decorator
def uppercase_decorator(function):
def wrapper():
result = function()
return result.upper()
return wrapper
@uppercase_decorator
def greet():
return "hello, world"
print(greet()) # "HELLO, WORLD"
# Decorator with arguments
def repeat(times):
def decorator(function):
def wrapper(*args, **kwargs):
for _ in range(times):
result = function(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
# Preserving metadata
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Before function call")
result = func(*args, **kwargs)
print("After function call")
return result
return wrapper
@my_decorator
def example_function():
"""This is an example function"""
print("Function is running")
print(example_function.__name__) # 'example_function'
print(example_function.__doc__) # 'This is an example function'
# Class decorators
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self):
print("Database connection created")
db1 = Database()
db2 = Database() # Same instance
print(db1 is db2) # True
# Built-in decorators
class MyClass:
@staticmethod
def static_method():
return "This is a static method"
@classmethod
def class_method(cls):
return f"This is a class method of {cls.__name__}"
@property
def my_property(self):
return "This is a property"
Generators
# Generator function
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
# Using generator
counter = count_up_to(5)
print(next(counter)) # 1
print(next(counter)) # 2
for num in count_up_to(5):
print(num)
# Generator expression
squares = (x**2 for x in range(10))
print(next(squares)) # 0
print(next(squares)) # 1
# List comprehension vs Generator expression
list_comp = [x**2 for x in range(1000000)] # Creates full list in memory
gen_exp = (x**2 for x in range(1000000)) # Creates values on demand
# Practical generator example
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib))
# Generator with send()
def echo():
while True:
value = yield
print(f"Received: {value}")
gen = echo()
next(gen) # Prime the generator
gen.send("Hello")
gen.send("World")
# Reading large files
def read_large_file(file_path):
with open(file_path, 'r') as file:
for line in file:
yield line.strip()
# for line in read_large_file('huge_file.txt'):
# process(line)
Iterators
# Creating an iterator class
class Counter:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current > self.end:
raise StopIteration
self.current += 1
return self.current - 1
counter = Counter(1, 5)
for num in counter:
print(num)
# Using iter() and next()
my_list = [1, 2, 3, 4]
iterator = iter(my_list)
print(next(iterator)) # 1
print(next(iterator)) # 2
# Infinite iterator
from itertools import count, cycle, repeat
# count(start, step)
for num in count(10, 2):
if num > 20:
break
print(num) # 10, 12, 14, 16, 18, 20
# cycle(iterable)
colors = cycle(['red', 'green', 'blue'])
for _ in range(7):
print(next(colors)) # red, green, blue, red, green, blue, red
# repeat(value, times)
for num in repeat(5, 3):
print(num) # 5, 5, 5
Context Managers
# Using with statement
with open('file.txt', 'r') as file:
content = file.read()
# File automatically closed
# Creating custom context manager (class-based)
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
if exc_type is not None:
print(f"An exception occurred: {exc_val}")
return False # Propagate exception
with FileManager('test.txt', 'w') as f:
f.write('Hello, World!')
# Creating context manager with contextlib
from contextlib import contextmanager
@contextmanager
def file_manager(filename, mode):
file = open(filename, mode)
try:
yield file
finally:
file.close()
with file_manager('test.txt', 'w') as f:
f.write('Hello, World!')
# Practical example: Database connection
@contextmanager
def database_connection(db_name):
# Setup
print(f"Connecting to {db_name}")
connection = f"Connection to {db_name}"
try:
yield connection
finally:
# Teardown
print(f"Closing connection to {db_name}")
with database_connection("mydb") as conn:
print(f"Using {conn}")
List, Dict, and Set Comprehensions
# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
matrix = [[i*j for j in range(3)] for i in range(3)]
# With if-else
numbers = [1, 2, 3, 4, 5]
result = [x*2 if x % 2 == 0 else x for x in numbers]
# Nested comprehension
flattened = [num for row in matrix for num in row]
# Dictionary comprehension
squares_dict = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
word_lengths = {word: len(word) for word in ["apple", "banana", "cherry"]}
# {'apple': 5, 'banana': 6, 'cherry': 6}
# Swap keys and values
original = {'a': 1, 'b': 2, 'c': 3}
swapped = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
# Set comprehension
unique_squares = {x**2 for x in [1, 2, 2, 3, 3, 4]}
# {1, 4, 9, 16}
# Generator expression (not a comprehension, but similar)
gen = (x**2 for x in range(1000000)) # Memory efficient
Advanced Function Concepts
# *args and **kwargs
def flexible_function(*args, **kwargs):
print("Positional arguments:", args)
print("Keyword arguments:", kwargs)
flexible_function(1, 2, 3, name="John", age=30)
# Unpacking
def add(a, b, c):
return a + b + c
numbers = [1, 2, 3]
result = add(*numbers) # Unpacking list
person = {'a': 1, 'b': 2, 'c': 3}
result = add(**person) # Unpacking dictionary
# Closures
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
add_five = outer_function(5)
print(add_five(3)) # 8
print(add_five(10)) # 15
# Partial functions
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5)) # 25
print(cube(3)) # 27
# Function annotations (type hints)
def greet(name: str, age: int) -> str:
return f"{name} is {age} years old"
def calculate(x: float, y: float) -> float:
return x + y
# Recursive functions
def factorial(n):
if n == 0 or n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Memoization for optimization
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci_optimized(n):
if n <= 1:
return n
return fibonacci_optimized(n-1) + fibonacci_optimized(n-2)
print(fibonacci_optimized(100)) # Much faster
11. Standard Library Deep Dive
Collections Module
from collections import Counter, defaultdict, OrderedDict, deque, namedtuple
# Counter - count occurrences
text = "hello world hello"
word_count = Counter(text.split())
print(word_count) # Counter({'hello': 2, 'world': 1})
print(word_count.most_common(1)) # [('hello', 2)]
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
num_count = Counter(numbers)
print(num_count[4]) # 4
# defaultdict - provide default values
dd = defaultdict(list)
dd['colors'].append('red')
dd['colors'].append('blue')
print(dd['colors']) # ['red', 'blue']
print(dd['numbers']) # []
word_index = defaultdict(list)
text = "the quick brown fox jumps over the lazy dog"
for index, word in enumerate(text.split()):
word_index[len(word)].append(word)
# OrderedDict - maintains insertion order (less needed in Python 3.7+)
od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3
# deque - double-ended queue (efficient append/pop from both ends)
dq = deque([1, 2, 3, 4, 5])
dq.append(6) # Add to right
dq.appendleft(0) # Add to left
dq.pop() # Remove from right
dq.popleft() # Remove from left
dq.rotate(2) # Rotate right
print(dq)
# namedtuple - lightweight object
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
print(p.x, p.y)
print(p[0], p[1])
Person = namedtuple('Person', ['name', 'age', 'city'])
person = Person('Alice', 30, 'NYC')
print(person.name)
Datetime Module
from datetime import datetime, date, time, timedelta
# Current date and time
now = datetime.now()
print(now)
today = date.today()
print(today)
# Create specific date/time
dt = datetime(2024, 12, 25, 10, 30, 0)
print(dt)
d = date(2024, 12, 25)
t = time(10, 30, 45)
# Formatting
print(now.strftime("%Y-%m-%d %H:%M:%S"))
print(now.strftime("%B %d, %Y"))
print(now.strftime("%A, %I:%M %p"))
# Parsing
date_string = "2024-12-25 10:30:00"
dt = datetime.strptime(date_string, "%Y-%m-%d %H:%M:%S")
# Date arithmetic
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
past = today - timedelta(days=30)
# Time difference
date1 = datetime(2024, 1, 1)
date2 = datetime(2024, 12, 31)
diff = date2 - date1
print(diff.days) # Days between dates
# Components
print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)
print(now.weekday()) # Monday is 0
print(now.isoweekday()) # Monday is 1
# Timezone-aware datetime
from datetime import timezone
utc_now = datetime.now(timezone.utc)
print(utc_now)
Regular Expressions (re module)
import re
# Basic matching
text = "The quick brown fox"
match = re.search(r'quick', text)
if match:
print("Found:", match.group())
# Finding all matches
text = "cat bat rat mat"
matches = re.findall(r'.at', text)
print(matches) # ['cat', 'bat', 'rat', 'mat']
# Substitution
text = "Hello World"
new_text = re.sub(r'World', 'Python', text)
print(new_text) # "Hello Python"
# Splitting
text = "apple,banana;orange:grape"
fruits = re.split(r'[,;:]', text)
print(fruits)
# Pattern matching
email = "user@example.com"
pattern = r'^[\w\.-]+@[\w\.-]+\.\w+
if re.match(pattern, email):
print("Valid email")
# Groups
text = "John Doe, age 30"
pattern = r'(\w+) (\w+), age (\d+)'
match = re.search(pattern, text)
if match:
print(match.group(1)) # John
print(match.group(2)) # Doe
print(match.group(3)) # 30
print(match.groups()) # ('John', 'Doe', '30')
# Named groups
pattern = r'(?P<first>\w+) (?P<last>\w+), age (?P<age>\d+)'
match = re.search(pattern, text)
if match:
print(match.group('first'))
print(match.groupdict())
# Common patterns
"""
. - Any character except newline
^ - Start of string
$ - End of string
* - 0 or more repetitions
+ - 1 or more repetitions
? - 0 or 1 repetition
{m,n} - Between m and n repetitions
[] - Character set
| - OR operator
() - Grouping
\d - Digit [0-9]
\D - Non-digit
\w - Word character [a-zA-Z0-9_]
\W - Non-word character
\s - Whitespace
\S - Non-whitespace
"""
# Practical examples
# Phone number
phone = "123-456-7890"
if re.match(r'\d{3}-\d{3}-\d{4}', phone):
print("Valid phone number")
# URL
url = "https://www.example.com/page"
if re.match(r'https?://[\w\.-]+\.\w+', url):
print("Valid URL")
# Extract numbers
text = "Price: $29.99, Discount: 15%"
numbers = re.findall(r'\d+\.?\d*', text)
print(numbers) # ['29.99', '15']
Itertools Module
from itertools import *
# count - infinite counter
for i in count(10, 2):
if i > 20:
break
print(i) # 10, 12, 14, 16, 18, 20
# cycle - infinite loop through iterable
colors = cycle(['red', 'green', 'blue'])
for _ in range(7):
print(next(colors))
# repeat - repeat value
for x in repeat(10, 3):
print(x) # 10, 10, 10
# chain - combine iterables
result = list(chain([1, 2], [3, 4], [5, 6]))
print(result) # [1, 2, 3, 4, 5, 6]
# compress - filter based on selectors
data = ['A', 'B', 'C', 'D']
selectors = [1, 0, 1, 0]
result = list(compress(data, selectors))
print(result) # ['A', 'C']
# dropwhile - drop while condition is true
data = [1, 4, 6, 4, 1]
result = list(dropwhile(lambda x: x < 5, data))
print(result) # [6, 4, 1]
# takewhile - take while condition is true
result = list(takewhile(lambda x: x < 5, data))
print(result) # [1, 4]
# groupby - group consecutive elements
data = [('A', 1), ('A', 2), ('B', 3), ('B', 4), ('C', 5)]
for key, group in groupby(data, lambda x: x[0]):
print(key, list(group))
# combinations - all combinations
result = list(combinations([1, 2, 3], 2))
print(result) # [(1, 2), (1, 3), (2, 3)]
# permutations - all permutations
result = list(permutations([1, 2, 3], 2))
print(result) # [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
# product - cartesian product
result = list(product([1, 2], ['a', 'b']))
print(result) # [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
# accumulate - cumulative operation
data = [1, 2, 3, 4, 5]
result = list(accumulate(data))
print(result) # [1, 3, 6, 10, 15]
result = list(accumulate(data, lambda x, y: x * y))
print(result) # [1, 2, 6, 24, 120]
Random Module
import random
# Random float between 0 and 1
print(random.random())
# Random integer
print(random.randint(1, 10)) # 1 to 10 inclusive
print(random.randrange(1, 10)) # 1 to 9
# Random float in range
print(random.uniform(1.5, 10.5))
# Random choice
fruits = ['apple', 'banana', 'cherry']
print(random.choice(fruits))
# Random sample (without replacement)
numbers = list(range(1, 51))
lottery = random.sample(numbers, 6)
print(lottery)
# Random choices (with replacement)
result = random.choices(['heads', 'tails'], k=10)
print(result)
# Weighted choices
result = random.choices(['red', 'green', 'blue'], weights=[10, 1, 1], k=10)
print(result)
# Shuffle in place
deck = list(range(52))
random.shuffle(deck)
print(deck)
# Set seed for reproducibility
random.seed(42)
print(random.random())
random.seed(42)
print(random.random()) # Same value
# Gaussian distribution
for _ in range(5):
print(random.gauss(0, 1)) # Mean 0, std dev 1
Math Module
import math
# Constants
print(math.pi)
print(math.e)
print(math.inf)
print(math.nan)
# Basic functions
print(math.sqrt(16)) # 4.0
print(math.pow(2, 3)) # 8.0
print(math.exp(2)) # e^2
print(math.log(10)) # Natural log
print(math.log10(100)) # Log base 10
print(math.log(8, 2)) # Log base 2
# Rounding
print(math.ceil(4.3)) # 5
print(math.floor(4.9)) # 4
print(math.trunc(4.9)) # 4
# Trigonometry
print(math.sin(math.pi/2)) # 1.0
print(math.cos(0)) # 1.0
print(math.tan(math.pi/4)) # 1.0
print(math.degrees(math.pi)) # 180.0
print(math.radians(180)) # pi
# Factorial and combinations
print(math.factorial(5)) # 120
print(math.comb(5, 2)) # 10 (5 choose 2)
print(math.perm(5, 2)) # 20 (5 permute 2)
# Other functions
print(math.gcd(48, 18)) # 6 (greatest common divisor)
print(math.isnan(float('nan'))) # True
print(math.isinf(math.inf)) # True
print(math.isfinite(100)) # True
12. Working with Data
Working with Lists (Advanced)
# Sorting
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort() # In-place sort
sorted_nums = sorted(numbers) # Returns new sorted list
# Sort with key
students = [
{"name": "Alice", "grade": 85},
{"name": "Bob", "grade": 92},
{"name": "Charlie", "grade": 78}
]
sorted_students = sorted(students, key=lambda x: x['grade'], reverse=True)
# Multiple sort keys
sorted_students = sorted(students, key=lambda x: (x['grade'], x['name']))
# Filter
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
# Map
squared = list(map(lambda x: x**2, numbers))
# Reduce
from functools import reduce
product = reduce(lambda x, y: x * y, numbers)
maximum = reduce(lambda x, y: x if x > y else y, numbers)
# Zip
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
cities = ["NYC", "LA", "Chicago"]
combined = list(zip(names, ages, cities))
# [('Alice', 25, 'NYC'), ('Bob', 30, 'LA'), ('Charlie', 35, 'Chicago')]
# Unzip
names, ages, cities = zip(*combined)
# All and any
numbers = [2, 4, 6, 8]
print(all(x % 2 == 0 for x in numbers)) # True
print(any(x > 5 for x in numbers)) # True
# List slicing tricks
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::2]) # [0, 2, 4, 6, 8]
print(numbers[1::2]) # [1, 3, 5, 7, 9]
print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
print(numbers[-3:]) # [7, 8, 9]
# Flattening nested lists
nested = [[1, 2], [3, 4], [5, 6]]
flattened = [num for row in nested for num in row]
# [1, 2, 3, 4, 5, 6]
# List intersection and difference
list1 = [1, 2, 3, 4, 5]
list2 = [4, 5, 6, 7, 8]
intersection = list(set(list1) & set(list2)) # [4, 5]
difference = list(set(list1) - set(list2)) # [1, 2, 3]
union = list(set(list1) | set(list2)) # [1, 2, 3, 4, 5, 6, 7, 8]
Working with Strings (Advanced)
# String methods
text = " Hello, World! "
print(text.strip()) # Remove whitespace
print(text.lstrip()) # Remove left whitespace
print(text.rstrip()) # Remove right whitespace
print(text.replace("World", "Python"))
print(text.split(",")) # Split by comma
print(" ".join(["Hello", "World"])) # Join with space
# String validation
print("123".isdigit()) # True
print("abc".isalpha()) # True
print("abc123".isalnum()) # True
print("Hello World".istitle()) # True
print("hello".islower()) # True
print("HELLO".isupper()) # True
# String formatting
name = "Alice"
age = 30
salary = 50000.5
# f-strings
print(f"{name} is {age} years old")
print(f"{name:>10}") # Right align
print(f"{name:<10}") # Left align
print(f"{name:^10}") # Center align
print(f"{salary:,.2f}") # 50,000.50
print(f"{age:05d}") # 00030
# format()
print("{} is {} years old".format(name, age))
print("{0} is {1} years old. {0} works hard.".format(name, age))
print("{name} is {age} years old".format(name=name, age=age))
# String manipulation
text = "Hello World"
print(text.startswith("Hello")) # True
print(text.endswith("World")) # True
print(text.find("World")) # 6
print(text.index("World")) # 6
print(text.count("l")) # 3
# String slicing
text = "Python Programming"
print(text[0:6]) # "Python"
print(text[7:]) # "Programming"
print(text[::-1]) # Reverse
# Multi-line strings
multiline = """Line 1
Line 2
Line 3"""
# Raw strings
path = r"C:\Users\name\folder"
# String translation
trans = str.maketrans("aeiou", "12345")
text = "hello world"
print(text.translate(trans)) # "h2ll4 w4rld"
13. Web Development
Flask Basics
# Install: pip install flask
from flask import Flask, request, jsonify, render_template
app = Flask(__name__)
# Simple route
@app.route('/')
def home():
return "Hello, World!"
# Route with variable
@app.route('/user/<username>')
def show_user(username):
return f"User: {username}"
# Route with multiple methods
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
return f"Logging in {username}"
return '''
<form method="post">
<input name="username">
<input name="password" type="password">
<input type="submit">
</form>
'''
# JSON API
@app.route('/api/users')
def get_users():
users = [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
return jsonify(users)
# Template rendering
@app.route('/template')
def template_example():
data = {
"title": "My Page",
"users": ["Alice", "Bob", "Charlie"]
}
return render_template('index.html', **data)
# Error handling
@app.errorhandler(404)
def not_found(error):
return "Page not found", 404
if __name__ == '__main__':
app.run(debug=True)
Requests Library
# Install: pip install requests
import requests
# GET request
response = requests.get('https://api.github.com')
print(response.status_code)
print(response.text)
print(response.json())
# GET with parameters
params = {'q': 'python', 'sort': 'stars'}
response = requests.get('https://api.github.com/search/repositories', params=params)
data = response.json()
# POST request
data = {'username': 'alice', 'password': 'secret'}
response = requests.post('https://httpbin.org/post', data=data)
# POST JSON
json_data = {'name': 'Alice', 'age': 30}
response = requests.post('https://httpbin.org/post', json=json_data)
# Headers
headers = {'Authorization': 'Bearer token123'}
response = requests.get('https://api.example.com/data', headers=headers)
# Timeout
response = requests.get('https://api.example.com', timeout=5)
# Error handling
try:
response = requests.get('https://api.example.com')
response.raise_for_status() # Raise exception for 4xx/5xx
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
# Session (maintains cookies)
session = requests.Session()
session.get('https://httpbin.org/cookies/set/sessioncookie/123')
response = session.get('https://httpbin.org/cookies')
# Download file
response = requests.get('https://example.com/file.pdf')
with open('file.pdf', 'wb') as f:
f.write(response.content)
14. Database Programming
SQLite
import sqlite3
# Connect to database (creates if doesn't exist)
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
# Create table
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
age INTEGER
)
''')
# Insert data
cursor.execute('''
INSERT INTO users (name, email, age)
VALUES (?, ?, ?)
''', ('Alice', 'alice@example.com', 30))
# Insert multiple
users = [
('Bob', 'bob@example.com', 25),
('Charlie', 'charlie@example.com', 35)
]
cursor.executemany('INSERT INTO users (name, email, age) VALUES (?, ?, ?)', users)
# Commit changes
conn.commit()
# Query data
cursor.execute('SELECT * FROM users')
all_users = cursor.fetchall()
print(all_users)
# Query with condition
cursor.execute('SELECT * FROM users WHERE age > ?', (25,))
filtered_users = cursor.fetchall()
# Fetch one
cursor.execute('SELECT * FROM users WHERE email = ?', ('alice@example.com',))
user = cursor.fetchone()
# Update data
cursor.execute('''
UPDATE users
SET age = ?
WHERE name = ?
''', (31, 'Alice'))
conn.commit()
# Delete data
cursor.execute('DELETE FROM users WHERE name = ?', ('Bob',))
conn.commit()
# Get column names
cursor.execute('SELECT * FROM users')
columns = [description[0] for description in cursor.description]
# Use Row factory for dict-like access
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute('SELECT * FROM users')
row = cursor.fetchone()
print(row['name'], row['email'])
# Transaction
try:
cursor.execute('INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
('David', 'david@example.com', 40))
cursor.execute('UPDATE users SET age = age + 1 WHERE name = ?', ('Alice',))
conn.commit()
except sqlite3.Error as e:
conn.rollback()
print(f"Error: {e}")
# Close connection
conn.close()
# Context manager (auto-commit and close)
with sqlite3.connect('example.db') as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM users')
users = cursor.fetchall()
MySQL with PyMySQL
# Install: pip install pymysql
import pymysql
# Connect to database
connection = pymysql.connect(
host='localhost',
user='root',
password='password',
database='mydb',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
try:
with connection.cursor() as cursor:
# Create table
cursor.execute('''
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10, 2),
quantity INT
)
''')
# Insert
with connection.cursor() as cursor:
sql = "INSERT INTO products (name, price, quantity) VALUES (%s, %s, %s)"
cursor.execute(sql, ('Product A', 29.99, 100))
connection.commit()
# Select
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM products WHERE price > %s", (20,))
products = cursor.fetchall()
for product in products:
print(product)
finally:
connection.close()
15. Testing & Debugging
Unit Testing
import unittest
# Code to test
def add(a, b):
return a + b
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
# Test class
class TestMathFunctions(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 3), 5)
self.assertEqual(add(-1, 1), 0)
self.assertEqual(add(0, 0), 0)
def test_is_prime(self):
self.assertTrue(is_prime(2))
self.assertTrue(is_prime(17))
self.assertFalse(is_prime(1))
self.assertFalse(is_prime(4))
def test_types(self):
self.assertIsInstance(add(1, 2), int)
self.assertIsInstance(is_prime(5), bool)
def test_exceptions(self):
with self.assertRaises(TypeError):
add("a", "b")
# Setup and teardown
def setUp(self):
"""Run before each test"""
self.test_list = [1, 2, 3]
def tearDown(self):
"""Run after each test"""
self.test_list = None
if __name__ == '__main__':
unittest.main()
Pytest
# Install: pip install pytest
# test_example.py
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
def test_add_strings():
assert add("hello", " world") == "hello world"
# Parametrize tests
import pytest
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300)
])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expected
# Fixtures
@pytest.fixture
def sample_data():
return [1, 2, 3, 4, 5]
def test_with_fixture(sample_data):
assert len(sample_data) == 5
assert sum(sample_data) == 15
# Test exceptions
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
def test_divide_by_zero():
with pytest.raises(ValueError):
divide(10, 0)
# Run tests: pytest test_example.py
Debugging
# Using print statements (basic debugging)
def calculate_total(prices):
print(f"Input prices: {prices}") # Debug
total = sum(prices)
print(f"Total: {total}") # Debug
return total
# Using pdb (Python debugger)
import pdb
def buggy_function(x, y):
pdb.set_trace() # Breakpoint
result = x + y
return result * 2
# pdb commands:
# n - next line
# s - step into function
# c - continue execution
# p variable - print variable
# l - list code
# q - quit debugger
# Using breakpoint() (Python 3.7+)
def another_function(x):
breakpoint() # Modern way
return x * 2
# Logging for debugging
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def process_data(data):
logger.debug(f"Processing data: {data}")
result = data * 2
logger.info(f"Result: {result}")
return result
# Assertions for debugging
def calculate_percentage(part, total):
assert total != 0, "Total cannot be zero"
assert part <= total, "Part cannot be greater than total"
return (part / total) * 100
# Try-except for debugging
def safe_divide(a, b):
try:
result = a / b
except Exception as e:
print(f"Error: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()
raise
return result
16. Best Practices & Design Patterns
Code Organization
# Good project structure
"""
project/
__init__.py
main.py
config.py
models/
__init__.py
user.py
product.py
services/
__init__.py
auth_service.py
data_service.py
utils/
__init__.py
helpers.py
validators.py
tests/
__init__.py
test_models.py
test_services.py
requirements.txt
README.md
"""
# PEP 8 Style Guide
# - Use 4 spaces for indentation
# - Maximum line length: 79 characters
# - Use snake_case for variables and functions
# - Use PascalCase for classes
# - Use UPPER_CASE for constants
# Good naming
MAX_CONNECTIONS = 100
user_count = 0
def calculate_total_price(items):
return sum(item.price for item in items)
class UserAccount:
def __init__(self, username):
self.username = username
# Type hints (Python 3.5+)
from typing import List, Dict, Optional, Union, Tuple
def greet(name: str) -> str:
return f"Hello, {name}"
def process_items(items: List[str]) -> Dict[str, int]:
return {item: len(item) for item in items}
def find_user(user_id: int) -> Optional[dict]:
# Returns dict or None
return None
def get_value(key: str) -> Union[int, str]:
# Returns either int or str
return 42
# Docstrings
def calculate_area(length: float, width: float) -> float:
"""
Calculate the area of a rectangle.
Args:
length (float): The length of the rectangle
width (float): The width of the rectangle
Returns:
float: The area of the rectangle
Raises:
ValueError: If length or width is negative
Examples:
>>> calculate_area(5, 3)
15.0
"""
if length < 0 or width < 0:
raise ValueError("Dimensions cannot be negative")
return length * width
Design Patterns
# 1. Singleton Pattern
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# Usage
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True
# 2. Factory Pattern
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class AnimalFactory:
@staticmethod
def create_animal(animal_type):
if animal_type == "dog":
return Dog()
elif animal_type == "cat":
return Cat()
else:
raise ValueError(f"Unknown animal type: {animal_type}")
# Usage
animal = AnimalFactory.create_animal("dog")
print(animal.speak())
# 3. Observer Pattern
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def detach(self, observer):
self._observers.remove(observer)
def notify(self, message):
for observer in self._observers:
observer.update(message)
class Observer:
def __init__(self, name):
self.name = name
def update(self, message):
print(f"{self.name} received: {message}")
# Usage
subject = Subject()
observer1 = Observer("Observer 1")
observer2 = Observer("Observer 2")
subject.attach(observer1)
subject.attach(observer2)
subject.notify("Hello Observers!")
# 4. Strategy Pattern
class SortStrategy:
def sort(self, data):
pass
class BubbleSort(SortStrategy):
def sort(self, data):
print("Sorting using bubble sort")
return sorted(data)
class QuickSort(SortStrategy):
def sort(self, data):
print("Sorting using quick sort")
return sorted(data)
class Sorter:
def __init__(self, strategy: SortStrategy):
self.strategy = strategy
def sort(self, data):
return self.strategy.sort(data)
# Usage
data = [3, 1, 4, 1, 5, 9, 2, 6]
sorter = Sorter(BubbleSort())
sorted_data = sorter.sort(data)
# 5. Decorator Pattern (not to confuse with @decorator)
class Coffee:
def cost(self):
return 5
class MilkDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self):
return self.coffee.cost() + 2
class SugarDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self):
return self.coffee.cost() + 1
# Usage
coffee = Coffee()
coffee_with_milk = MilkDecorator(coffee)
coffee_with_milk_and_sugar = SugarDecorator(coffee_with_milk)
print(coffee_with_milk_and_sugar.cost()) # 8
SOLID Principles
# S - Single Responsibility Principle
# Each class should have one responsibility
# Bad
class User:
def __init__(self, name):
self.name = name
def save_to_database(self):
# Database logic here
pass
def send_email(self):
# Email logic here
pass
# Good
class User:
def __init__(self, name):
self.name = name
class UserRepository:
def save(self, user):
# Database logic here
pass
class EmailService:
def send_email(self, user, message):
# Email logic here
pass
# O - Open/Closed Principle
# Open for extension, closed for modification
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
def calculate_total_area(shapes):
return sum(shape.area() for shape in shapes)
# L - Liskov Substitution Principle
# Derived classes must be substitutable for base classes
class Bird:
def move(self):
print("Moving")
class FlyingBird(Bird):
def move(self):
print("Flying")
class Penguin(Bird):
def move(self):
print("Walking")
# I - Interface Segregation Principle
# Clients shouldn't depend on interfaces they don't use
from abc import ABC, abstractmethod
class Printer(ABC):
@abstractmethod
def print(self, document):
pass
class Scanner(ABC):
@abstractmethod
def scan(self, document):
pass
class SimplePrinter(Printer):
def print(self, document):
print(f"Printing: {document}")
class MultiFunctionDevice(Printer, Scanner):
def print(self, document):
print(f"Printing: {document}")
def scan(self, document):
print(f"Scanning: {document}")
# D - Dependency Inversion Principle
# Depend on abstractions, not concretions
class Database(ABC):
@abstractmethod
def save(self, data):
pass
class MySQLDatabase(Database):
def save(self, data):
print(f"Saving to MySQL: {data}")
class MongoDBDatabase(Database):
def save(self, data):
print(f"Saving to MongoDB: {data}")
class UserService:
def __init__(self, database: Database):
self.database = database
def create_user(self, user_data):
self.database.save(user_data)
# Usage - easy to switch databases
mysql_db = MySQLDatabase()
user_service = UserService(mysql_db)
17. Real-World Projects
Project 1: Contact Management System
import json
from typing import List, Optional
class Contact:
def __init__(self, name: str, phone: str, email: str):
self.name = name
self.phone = phone
self.email = email
def to_dict(self):
return {
'name': self.name,
'phone': self.phone,
'email': self.email
}
@classmethod
def from_dict(cls, data):
return cls(data['name'], data['phone'], data['email'])
def __str__(self):
return f"{self.name} - {self.phone} - {self.email}"
class ContactManager:
def __init__(self, filename='contacts.json'):
self.filename = filename
self.contacts: List[Contact] = []
self.load_contacts()
def add_contact(self, contact: Contact):
self.contacts.append(contact)
self.save_contacts()
print(f"Contact {contact.name} added successfully")
def remove_contact(self, name: str):
self.contacts = [c for c in self.contacts if c.name != name]
self.save_contacts()
print(f"Contact {name} removed successfully")
def search_contact(self, name: str) -> Optional[Contact]:
for contact in self.contacts:
if contact.name.lower() == name.lower():
return contact
return None
def list_contacts(self):
if not self.contacts:
print("No contacts found")
return
print("\n--- Contact List ---")
for i, contact in enumerate(self.contacts, 1):
print(f"{i}. {contact}")
def update_contact(self, name: str, phone: str = None, email: str = None):
contact = self.search_contact(name)
if contact:
if phone:
contact.phone = phone
if email:
contact.email = email
self.save_contacts()
print(f"Contact {name} updated successfully")
else:
print(f"Contact {name} not found")
def save_contacts(self):
with open(self.filename, 'w') as f:
json.dump([c.to_dict() for c in self.contacts], f, indent=4)
def load_contacts(self):
try:
with open(self.filename, 'r') as f:
data = json.load(f)
self.contacts = [Contact.from_dict(c) for c in data]
except FileNotFoundError:
self.contacts = []
def main():
manager = ContactManager()
while True:
print("\n=== Contact Management System ===")
print("1. Add Contact")
print("2. Remove Contact")
print("3. Search Contact")
print("4. List All Contacts")
print("5. Update Contact")
print("6. Exit")
choice = input("\nEnter your choice: ")
if choice == '1':
name = input("Enter name: ")
phone = input("Enter phone: ")
email = input("Enter email: ")
contact = Contact(name, phone, email)
manager.add_contact(contact)
elif choice == '2':
name = input("Enter name to remove: ")
manager.remove_contact(name)
elif choice == '3':
name = input("Enter name to search: ")
contact = manager.search_contact(name)
if contact:
print(f"\nFound: {contact}")
else:
print("Contact not found")
elif choice == '4':
manager.list_contacts()
elif choice == '5':
name = input("Enter name to update: ")
phone = input("Enter new phone (or press Enter to skip): ")
email = input("Enter new email (or press Enter to skip): ")
manager.update_contact(name, phone or None, email or None)
elif choice == '6':
print("Goodbye!")
break
else:
print("Invalid choice. Please try again.")
if __name__ == "__main__":
main()
Project 2: Web Scraper
import requests
from bs4 import BeautifulSoup
import csv
from typing import List, Dict
class WebScraper:
def __init__(self, base_url: str):
self.base_url = base_url
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
def fetch_page(self, url: str) -> BeautifulSoup:
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
return BeautifulSoup(response.content, 'html.parser')
except requests.RequestException as e:
print(f"Error fetching {url}: {e}")
return None
def scrape_quotes(self) -> List[Dict]:
"""Example: Scraping quotes from quotes.toscrape.com"""
quotes = []
page = 1
while True:
url = f"{self.base_url}/page/{page}/"
soup = self.fetch_page(url)
if not soup:
break
quote_elements = soup.find_all('div', class_='quote')
if not quote_elements:
break
for quote_elem in quote_elements:
text = quote_elem.find('span', class_='text').text
author = quote_elem.find('small', class_='author').text
tags = [tag.text for tag in quote_elem.find_all('a', class_='tag')]
quotes.append({
'text': text,
'author': author,
'tags': ', '.join(tags)
})
print(f"Scraped page {page}")
page += 1
return quotes
def save_to_csv(self, data: List[Dict], filename: str):
if not data:
print("No data to save")
return
keys = data[0].keys()
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=keys)
writer.writeheader()
writer.writerows(data)
print(f"Data saved to {filename}")
# Usage
if __name__ == "__main__":
scraper = WebScraper("http://quotes.toscrape.com")
quotes = scraper.scrape_quotes()
scraper.save_to_csv(quotes, 'quotes.csv')
print(f"Scraped {len(quotes)} quotes")
Project 3: Task Scheduler
import schedule
import time
from datetime import datetime
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
class TaskScheduler:
def __init__(self):
self.tasks = []
def add_task(self, func, interval_type, interval_value, *args, **kwargs):
"""
Add a task to the scheduler
interval_type: 'seconds', 'minutes', 'hours', 'days'
"""
job = None
if interval_type == 'seconds':
job = schedule.every(interval_value).seconds.do(func, *args, **kwargs)
elif interval_type == 'minutes':
job = schedule.every(interval_value).minutes.do(func, *args, **kwargs)
elif interval_type == 'hours':
job = schedule.every(interval_value).hours.do(func, *args, **kwargs)
elif interval_type == 'days':
job = schedule.every(interval_value).days.do(func, *args, **kwargs)
if job:
self.tasks.append({
'job': job,
'function': func.__name__,
'interval': f"{interval_value} {interval_type}"
})
logging.info(f"Task '{func.__name__}' scheduled every {interval_value} {interval_type}")
def run(self):
logging.info("Task Scheduler started")
try:
while True:
schedule.run_pending()
time.sleep(1)
except KeyboardInterrupt:
logging.info("Task Scheduler stopped")
# Example tasks
def backup_database():
logging.info("Backing up database...")
# Backup logic here
logging.info("Database backup completed")
def send_report():
logging.info("Sending daily report...")
# Report logic here
logging.info("Report sent")
def cleanup_temp_files():
logging.info("Cleaning up temporary files...")
# Cleanup logic here
logging.info("Cleanup completed")
# Usage
if __name__ == "__main__":
scheduler = TaskScheduler()
# Schedule tasks
scheduler.add_task(backup_database, 'hours', 1)
scheduler.add_task(send_report, 'days', 1)
scheduler.add_task(cleanup_temp_files, 'minutes', 30)
# Run scheduler
scheduler.run()
Project 4: Password Manager
import json
import hashlib
import os
from cryptography.fernet import Fernet
import getpass
class PasswordManager:
def __init__(self, master_password: str):
self.master_password = master_password
self.key = self._generate_key()
self.cipher = Fernet(self.key)
self.passwords_file = 'passwords.enc'
self.passwords = self.load_passwords()
def _generate_key(self) -> bytes:
"""Generate encryption key from master password"""
password_hash = hashlib.sha256(self.master_password.encode()).digest()
return Fernet.generate_key() # In production, derive from password_hash
def add_password(self, service: str, username: str, password: str):
encrypted_password = self.cipher.encrypt(password.encode()).decode()
self.passwords[service] = {
'username': username,
'password': encrypted_password
}
self.save_passwords()
print(f"Password for {service} added successfully")
def get_password(self, service: str) -> dict:
if service in self.passwords:
encrypted_password = self.passwords[service]['password'].encode()
decrypted_password = self.cipher.decrypt(encrypted_password).decode()
return {
'username': self.passwords[service]['username'],
'password': decrypted_password
}
return None
def list_services(self):
if not self.passwords:
print("No passwords stored")
return
print("\n--- Stored Services ---")
for i, service in enumerate(self.passwords.keys(), 1):
print(f"{i}. {service}")
def remove_password(self, service: str):
if service in self.passwords:
del self.passwords[service]
self.save_passwords()
print(f"Password for {service} removed")
else:
print(f"Service {service} not found")
def save_passwords(self):
with open(self.passwords_file, 'w') as f:
json.dump(self.passwords, f, indent=4)
def load_passwords(self) -> dict:
try:
with open(self.passwords_file, 'r') as f:
return json.load(f)
except FileNotFoundError:
return {}
def main():
print("=== Password Manager ===")
master_password = getpass.getpass("Enter master password: ")
manager = PasswordManager(master_password)
while True:
print("\n1. Add Password")
print("2. Get Password")
print("3. List Services")
print("4. Remove Password")
print("5. Exit")
choice = input("\nEnter choice: ")
if choice == '1':
service = input("Enter service name: ")
username = input("Enter username: ")
password = getpass.getpass("Enter password: ")
manager.add_password(service, username, password)
elif choice == '2':
service = input("Enter service name: ")
credentials = manager.get_password(service)
if credentials:
print(f"\nUsername: {credentials['username']}")
print(f"Password: {credentials['password']}")
else:
print("Service not found")
elif choice == '3':
manager.list_services()
elif choice == '4':
service = input("Enter service name: ")
manager.remove_password(service)
elif choice == '5':
break
if __name__ == "__main__":
main()
18. Interview Questions & Answers
Basic Questions
Q1: What is Python? What are its key features?
A: Python is a high-level, interpreted, general-purpose programming language. Key features include:
- Easy to learn and read
- Interpreted (no compilation needed)
- Dynamically typed
- Object-oriented
- Extensive standard library
- Cross-platform
- Supports multiple paradigms (OOP, functional, procedural)
Q2: What is PEP 8?
A: PEP 8 is the Python Enhancement Proposal that provides style guidelines for Python code. Key rules:
- Use 4 spaces for indentation
- Maximum line length of 79 characters
- Use snake_case for functions and variables
- Use PascalCase for classes
- Use blank lines to separate functions and classes
Q3: What is the difference between list and tuple?
A:
- List: Mutable, uses square brackets
[], slower, more memory - Tuple: Immutable, uses parentheses
(), faster, less memory - Lists for data that changes, tuples for fixed data
Q4: Explain *args and **kwargs.
A:
*args: Variable positional arguments (tuple)**kwargs: Variable keyword arguments (dictionary)
def example(*args, **kwargs):
print(args) # (1, 2, 3)
print(kwargs) # {'a': 1, 'b': 2}
example(1, 2, 3, a=1, b=2)
Q5: What is list comprehension?
A: Concise way to create lists:
# Traditional
squares = []
for x in range(10):
squares.append(x**2)
# List comprehension
squares = [x**2 for x in range(10)]
# With condition
evens = [x for x in range(20) if x % 2 == 0]
Intermediate Questions
Q6: Explain Python’s GIL (Global Interpreter Lock).
A: GIL is a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecode simultaneously. This means:
- Only one thread executes Python code at a time
- Impacts CPU-bound multi-threaded programs
- I/O-bound programs less affected
- Use multiprocessing for true parallelism
Q7: What are decorators?
A: Functions that modify the behavior of other functions:
def uppercase_decorator(func):
def wrapper():
result = func()
return result.upper()
return wrapper
@uppercase_decorator
def greet():
return "hello"
print(greet()) # "HELLO"
Q8: What is the difference between __str__ and __repr__?
A:
__str__: Human-readable string (for end users)__repr__: Unambiguous representation (for developers)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name}, {self.age} years old"
def __repr__(self):
return f"Person('{self.name}', {self.age})"
Q9: Explain the difference between deep copy and shallow copy.
A:
import copy
original = [[1, 2], [3, 4]]
# Shallow copy - copies reference
shallow = copy.copy(original)
shallow[0][0] = 99
print(original) # [[99, 2], [3, 4]] - affected!
# Deep copy - copies all nested objects
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0][0] = 99
print(original) # [[1, 2], [3, 4]] - not affected
Q10: What are generators? Why use them?
A: Functions that yield values one at a time, useful for memory efficiency:
# Generator
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
# Uses minimal memory
for num in count_up_to(1000000):
print(num)
Advanced Questions
Q11: Explain metaclasses.
A: Classes that create classes. The type metaclass creates all classes in Python:
# Creating class with type
MyClass = type('MyClass', (object,), {'x': 5})
# Custom metaclass
class Meta(type):
def __new__(cls, name, bases, attrs):
attrs['created_by'] = 'Meta'
return super().__new__(cls, name, bases, attrs)
class MyClass(metaclass=Meta):
pass
print(MyClass.created_by) # 'Meta'
Q12: What is monkey patching?
A: Dynamically modifying a class or module at runtime:
class MyClass:
def method(self):
return "original"
# Monkey patch
def new_method(self):
return "patched"
MyClass.method = new_method
obj = MyClass()
print(obj.method()) # "patched"
Q13: Explain the with statement and context managers.
A: Ensures proper resource management:
# Without context manager
file = open('file.txt', 'r')
try:
content = file.read()
finally:
file.close()
# With context manager
with open('file.txt', 'r') as file:
content = file.read()
# File automatically closed
# Custom context manager
from contextlib import contextmanager
@contextmanager
def my_context():
print("Enter")
yield
print("Exit")
with my_context():
print("Inside")
Q14: What is the difference between @staticmethod and @classmethod?
A:
class MyClass:
class_var = "class variable"
@staticmethod
def static_method(x):
# No access to class or instance
return x * 2
@classmethod
def class_method(cls, x):
# Has access to class
return f"{cls.class_var}: {x}"
print(MyClass.static_method(5)) # 10
print(MyClass.class_method(5)) # "class variable: 5"
Q15: How does Python’s garbage collection work?
A: Python uses:
- Reference Counting: Tracks number of references to an object
- Cycle Detection: Finds and collects circular references
- Generational Collection: Objects are divided into 3 generations
import gc
# Disable automatic GC
gc.disable()
# Manual collection
gc.collect()
# Get stats
print(gc.get_stats())
Coding Questions
Q16: Reverse a string
# Method 1: Slicing
def reverse_string(s):
return s[::-1]
# Method 2: Using reversed()
def reverse_string(s):
return ''.join(reversed(s))
# Method 3: Manual
def reverse_string(s):
result = ""
for char in s:
result = char + result
return result
Q17: Find duplicates in a list
def find_duplicates(lst):
seen = set()
duplicates = set()
for item in lst:
if item in seen:
duplicates.add(item)
else:
seen.add(item)
return list(duplicates)
# Or using Counter
from collections import Counter
def find_duplicates(lst):
counts = Counter(lst)
return [item for item, count in counts.items() if count > 1]
Q18: Fibonacci sequence
“`python
Recursive (inefficient)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
Iterative (efficient)
def fibonacci(n):
if n <= 1:
return n
a, b = 0, 1
for _ in range(n-1):
a, b = b, a + b
return b