Introduction to Python
Beginner · 5 min read
Python is a high-level, dynamically typed, general-purpose language known for its clean, readable syntax. It is used in web development (Django/Flask/FastAPI), data science, machine learning, automation, scripting, and more.
Why Python?
- Clean, readable syntax — feels like English
- Huge standard library + 400,000+ packages on PyPI
- Dominant in data science, ML (NumPy, Pandas, TensorFlow)
- Excellent for scripting and automation
- CPython (reference), PyPy (fast JIT), Jython (JVM)
- Current version: Python 3.12+
Running Python
# Check version
python3 --version
# Run a script
python3 hello.py
# Interactive REPL
python3
>>> print("Hello, World!")
Hello, World!
# hello.py
print("Hello, World!")
💡 Python uses indentation (4 spaces) to define code blocks — there are no curly braces. Consistent indentation is mandatory, not optional.
Variables & Data Types
Beginner · 8 min read
# No type declaration needed — Python infers types
name = "Aftab" # str
age = 28 # int
salary = 75000.50 # float
is_active = True # bool (capital T/F)
nothing = None # NoneType (like null)
big = 10 ** 100 # int — Python has arbitrary precision!
# Multiple assignment
x = y = z = 0
a, b, c = 1, 2, 3
a, *rest = [1, 2, 3, 4] # a=1, rest=[2,3,4]
# type() and isinstance()
type(name) # <class 'str'>
isinstance(age, int) # True
# Type conversion
int("42") # 42
float("3.14") # 3.14
str(100) # '100'
bool(0) # False (0, "", [], {}, None are falsy)
# Strings — immutable
s = "Hello, World!"
s[0] # 'H'
s[-1] # '!'
s[0:5] # 'Hello' (slicing)
s.lower() # 'hello, world!'
s.upper() # 'HELLO, WORLD!'
s.split(', ') # ['Hello', 'World!']
' hello '.strip() # 'hello'
s.replace('World', 'Python')
# f-strings (Python 3.6+) — preferred
msg = f"Name: {name}, Age: {age:.2f}"
expr = f"2 + 2 = {2 + 2}" # expressions inside {}
Operators
Beginner · 6 min read
# Arithmetic
10 + 3 # 13
10 - 3 # 7
10 * 3 # 30
10 / 3 # 3.333... (always float)
10 // 3 # 3 (floor division)
10 % 3 # 1 (modulus)
2 ** 10 # 1024 (exponentiation)
# Comparison
5 == 5 # True
5 != 6 # True
5 > 3 # True
5 <= 5 # True
# Chained comparison (Pythonic!)
1 < x < 10 # True if x in (1,10)
# Logical
True and False # False
True or False # True
not True # False
# Identity & Membership
x is None # True if x is None (identity check)
x is not None
'hello' in 'hello world' # True
3 in [1, 2, 3] # True
'key' not in my_dict # True
# Walrus operator := (Python 3.8+) — assign in expression
while chunk := f.read(1024):
process(chunk)
Control Flow
Beginner · 8 min read
# if / elif / else
score = 85
if score >= 90:
grade = 'A'
elif score >= 75:
grade = 'B'
else:
grade = 'C'
# Inline ternary
label = 'Pass' if score >= 50 else 'Fail'
# for loop (iterates over any iterable)
for i in range(5): # 0,1,2,3,4
print(i)
for i in range(2, 10, 2): # 2,4,6,8
print(i)
# for + enumerate
for idx, val in enumerate(['a', 'b', 'c'], start=1):
print(f"{idx}: {val}")
# for + zip (iterate parallel iterables)
for name, score in zip(names, scores):
print(f"{name}: {score}")
# while
n = 0
while n < 5:
n += 1
# break, continue, else (for/while with else — Pythonic)
for i in range(10):
if i == 5: break
else:
print("Loop completed without break")
# match statement (Python 3.10+ — structural pattern matching)
match command:
case "quit": quit_game()
case "start": start_game()
case _: print("Unknown command")
Functions
Beginner · 10 min read
def greet(name, greeting="Hello"):
"""Docstring: greet a user."""
return f"{greeting}, {name}!"
greet("Aftab") # Hello, Aftab!
greet("Aftab", "Hi") # Hi, Aftab!
greet(greeting="Hey", name="Aftab") # keyword args
# *args — variable positional arguments
def total(*nums):
return sum(nums)
total(1, 2, 3) # 6
# **kwargs — variable keyword arguments
def create_user(**fields):
return fields
create_user(name="Aftab", role="admin")
# Lambda — anonymous one-liner function
square = lambda x: x ** 2
add = lambda a, b: a + b
numbers = [3, 1, 4, 1, 5]
numbers.sort(key=lambda x: -x) # sort descending
# Higher-order functions
list(map(lambda x: x*2, [1,2,3])) # [2,4,6]
list(filter(lambda x: x%2==0, [1,2,3,4])) # [2,4]
sorted(users, key=lambda u: u['name'])
# Unpacking into function calls
args = (1, 2)
kwargs = {'greeting': 'Hey'}
greet(*args) # positional unpack
greet(**kwargs) # keyword unpack
Lists & Tuples
Beginner · 10 min read
# List — mutable, ordered, allows duplicates
fruits = ['apple', 'banana', 'cherry']
fruits[0] # 'apple'
fruits[-1] # 'cherry'
fruits[1:3] # ['banana','cherry']
fruits[::-1] # reversed
fruits.append('date')
fruits.insert(1, 'avocado')
fruits.extend(['elderberry', 'fig'])
fruits.remove('banana') # remove by value
fruits.pop() # remove & return last
fruits.pop(0) # remove & return at index
fruits.index('cherry') # find index
fruits.count('apple') # frequency
fruits.sort() # in-place sort
sorted(fruits) # returns new sorted list
fruits.reverse()
len(fruits) # length
# Tuple — immutable, ordered
point = (3, 4)
single = (42,) # trailing comma makes it a tuple
x, y = point # unpacking
print(point[0]) # 3
# Named tuple
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
p.x, p.y # 3, 4
Sets & Dictionaries
Beginner · 10 min read
# Set — unordered, unique values, mutable
s = {1, 2, 3, 3} # {1, 2, 3}
s.add(4)
s.discard(2) # no error if not found
s.remove(3) # raises KeyError if not found
len(s)
a = {1, 2, 3}
b = {2, 3, 4}
a | b # union: {1,2,3,4}
a & b # intersection: {2,3}
a - b # difference: {1}
a ^ b # symmetric diff: {1,4}
# Dict — key-value, ordered (Python 3.7+), mutable
user = {
'name': 'Aftab',
'age': 28,
'city': 'Noida'
}
user['name'] # 'Aftab'
user.get('email', 'N/A') # 'N/A' (no KeyError)
user['email'] = 'a@b.com'
del user['city']
user.pop('age') # removes & returns value
user.keys() # dict_keys(['name','email'])
user.values()
user.items() # dict_items([('name','Aftab'),...])
user.update({'role': 'admin'})
# Dict comprehension
squares = {x: x**2 for x in range(5)} # {0:0, 1:1, 2:4...}
# defaultdict
from collections import defaultdict
word_count = defaultdict(int)
for w in words: word_count[w] += 1 # no KeyError
Comprehensions
Intermediate · 7 min read
# List comprehension — [expression for item in iterable if condition]
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
flat = [n for row in matrix for n in row] # flatten 2D
upper = [s.upper() for s in names if s.startswith('A')]
# Dict comprehension
inverted = {v: k for k, v in my_dict.items()}
filtered = {k: v for k, v in d.items() if v > 0}
# Set comprehension
unique_len = {len(word) for word in words}
# Generator expression — lazy, memory-efficient
gen = (x**2 for x in range(1_000_000)) # no list in memory!
next(gen) # 0 (compute one at a time)
sum(x**2 for x in range(100)) # pass generator to built-ins
💡 Use generator expressions inside sum(), any(), all(), max(), min() instead of list comprehensions to avoid building an intermediate list.
Classes & Objects
Intermediate · 10 min read
class BankAccount:
# Class variable (shared by all instances)
bank_name = "SDS Bank"
_accounts = 0
def __init__(self, owner, balance=0):
# Instance variables
self.owner = owner
self._balance = balance # _prefix: convention for private
BankAccount._accounts += 1
def deposit(self, amount):
if amount <= 0: raise ValueError("Amount must be positive")
self._balance += amount
return self # method chaining
def withdraw(self, amount):
if amount > self._balance:
raise ValueError("Insufficient funds")
self._balance -= amount
return self
# Property — controlled attribute access
@property
def balance(self): return self._balance
# Class method
@classmethod
def total_accounts(cls): return cls._accounts
# Static method (no self or cls)
@staticmethod
def validate_amount(amount): return amount > 0
acc = BankAccount("Aftab", 1000)
acc.deposit(500).withdraw(200) # chaining
acc.balance # 1300
Inheritance & Polymorphism
Intermediate · 10 min read
class Shape:
def __init__(self, color="white"):
self.color = color
def area(self):
raise NotImplementedError("Subclasses must implement area()")
def describe(self):
return f"A {self.color} shape with area {self.area():.2f}"
class Circle(Shape):
def __init__(self, radius, **kwargs):
super().__init__(**kwargs)
self.radius = radius
def area(self): return 3.14159 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, w, h, **kwargs):
super().__init__(**kwargs)
self.w, self.h = w, h
def area(self): return self.w * self.h
# Polymorphism — same interface, different behavior
shapes = [Circle(5, color='red'), Rectangle(4, 6)]
for s in shapes:
print(s.describe()) # works for all Shape subclasses
# isinstance / issubclass
isinstance(Circle(1), Shape) # True
issubclass(Circle, Shape) # True
# Abstract Base Class (enforces interface)
from abc import ABC, abstractmethod
class Vehicle(ABC):
@abstractmethod
def start(self): ...
Dunder / Magic Methods
Intermediate · 10 min read
Dunder (double underscore) methods let you define how your objects behave with built-in Python operations like +, len(), print(), comparisons, and more.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self): # for developers: repr(obj)
return f"Vector({self.x}, {self.y})"
def __str__(self): # for users: str(obj) / print()
return f"({self.x}, {self.y})"
def __add__(self, other): # v1 + v2
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar): # v * 3
return Vector(self.x * scalar, self.y * scalar)
def __len__(self): # len(v)
return int((self.x**2 + self.y**2) ** 0.5)
def __eq__(self, other): # v1 == v2
return self.x == other.x and self.y == other.y
def __bool__(self): # bool(v) / if v:
return bool(self.x or self.y)
def __contains__(self, val): # val in v
return val in (self.x, self.y)
v1 = Vector(2, 3)
v2 = Vector(1, 4)
v1 + v2 # Vector(3, 7)
print(v1) # (2, 3)
len(v1) # 3
Decorators
Advanced · 10 min read
A decorator is a function that wraps another function to add behavior without modifying it.
# Basic decorator
def timer(func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time()-start:.3f}s")
return result
return wrapper
@timer # equivalent to: my_func = timer(my_func)
def slow_function(): ...
# Decorator with arguments
def retry(times=3, delay=1):
def decorator(func):
import time, functools
@functools.wraps(func) # preserve metadata
def wrapper(*args, **kwargs):
for i in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
if i == times - 1: raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=5, delay=2)
def call_external_api(): ...
# Class decorator
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 DatabaseConnection: ...
Generators & Iterators
Advanced · 8 min read
# Generator function — uses yield
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
gen = fibonacci()
[next(gen) for _ in range(8)] # [0,1,1,2,3,5,8,13]
# Finite generator with return
def chunked(lst, size):
for i in range(0, len(lst), size):
yield lst[i:i+size]
list(chunked(range(10), 3)) # [[0,1,2],[3,4,5],[6,7,8],[9]]
# Custom iterator protocol
class Countdown:
def __init__(self, n): self.n = n
def __iter__(self): return self
def __next__(self):
if self.n <= 0: raise StopIteration
self.n -= 1
return self.n + 1
for n in Countdown(3): print(n) # 3, 2, 1
# itertools — powerful iterator tools
from itertools import chain, islice, groupby, product, combinations
list(chain([1,2], [3,4])) # [1,2,3,4]
list(islice(fibonacci(), 10)) # first 10 Fibonacci numbers
list(combinations('ABC', 2)) # [('A','B'),('A','C'),('B','C')]
Context Managers
Advanced · 8 min read
Context managers handle setup and teardown automatically — used with the with statement. Most common use: file handling, database connections, locks.
# Using a context manager
with open('file.txt', 'r') as f:
content = f.read()
# file is auto-closed even if exception occurs
# Custom context manager via class
class Timer:
import time
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Elapsed: {time.time() - self.start:.3f}s")
return False # False = don't suppress exceptions
with Timer() as t:
run_heavy_task()
# contextlib — easier custom managers
from contextlib import contextmanager
@contextmanager
def db_transaction(connection):
try:
yield connection
connection.commit()
except:
connection.rollback()
raise
with db_transaction(conn) as db:
db.execute("INSERT INTO ...")
File Handling
Intermediate · 8 min read
# Reading files
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read() # entire file as string
lines = f.readlines() # list of lines
line = f.readline() # one line
for line in f: # memory-efficient iteration
print(line.strip())
# Writing files
with open('output.txt', 'w') as f:
f.write("Hello\n")
f.writelines(["line1\n", "line2\n"])
# Append
with open('log.txt', 'a') as f:
f.write("New log entry\n")
# JSON
import json
with open('config.json') as f:
config = json.load(f)
with open('out.json', 'w') as f:
json.dump(data, f, indent=2)
# CSV
import csv
with open('data.csv') as f:
reader = csv.DictReader(f)
rows = [row for row in reader]
# pathlib — modern file paths (Python 3.4+)
from pathlib import Path
p = Path('data/users.json')
p.exists()
p.read_text() # one-liner read
p.write_text("hello") # one-liner write
p.parent # Path('data')
p.glob('*.json') # pattern matching
Error Handling
Intermediate · 8 min read
# Basic try/except
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero")
except (TypeError, ValueError) as e:
print(f"Error: {e}")
except Exception as e:
print(f"Unexpected: {e}")
raise # re-raise
else:
print("No exception occurred") # runs if no except triggered
finally:
print("Always runs — cleanup here")
# Custom exceptions
class ValidationError(ValueError):
def __init__(self, field, message):
super().__init__(f"[{field}] {message}")
self.field = field
raise ValidationError("email", "Invalid format")
# Exception chaining
try:
connect_db()
except ConnectionError as e:
raise RuntimeError("App startup failed") from e
# Common built-in exceptions
# ValueError, TypeError, KeyError, IndexError
# AttributeError, FileNotFoundError, ImportError
# OSError, PermissionError, TimeoutError
# StopIteration, RecursionError, MemoryError
Modules & Packages
Intermediate · 8 min read
# Importing
import math
import os, sys
from pathlib import Path
from datetime import datetime, timedelta
import numpy as np # alias
# Creating a module (utils.py)
def add(a, b): return a + b
PI = 3.14159
# Package structure
myapp/
__init__.py # makes it a package
models.py
utils.py
api/
__init__.py
routes.py
# pip — package manager
pip install requests
pip install "fastapi==0.100.0"
pip freeze > requirements.txt
pip install -r requirements.txt
# Virtual environment (venv)
python3 -m venv venv
source venv/bin/activate # Mac/Linux
venv\Scripts\activate.bat # Windows
pip install flask
deactivate
# Useful standard library modules
import os # OS operations
import sys # Python runtime
import re # regex
import json # JSON
import datetime # dates
import random # random numbers
import hashlib # hashing
import logging # logging
import threading # threads
import subprocess # shell commands
Regular Expressions
Advanced · 8 min read
import re
# Compile for reuse
pattern = re.compile(r'\d{4}-\d{2}-\d{2}') # ISO date
# Functions
re.match(r'\d+', '123abc') # match at START
re.search(r'\d+', 'abc123') # search ANYWHERE
re.findall(r'\d+', 'a1b2c3') # ['1','2','3']
re.sub(r'\s+', ' ', text) # replace multiple spaces
re.split(r'[,;]', 'a,b;c') # ['a','b','c']
# Groups
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-06-15')
m.group(0) # '2024-06-15' (full match)
m.group(1) # '2024'
# Named groups
m = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})', '2024-06')
m.group('year') # '2024'
# Flags
re.search(r'hello', text, re.IGNORECASE | re.MULTILINE)
# Common patterns
EMAIL = r'^[\w.+-]+@[\w-]+\.\w{2,}$'
PHONE = r'^\+?[\d\s\-]{10,13}$'
URL = r'https?://[^\s]+'
IP = r'\b\d{1,3}(\.\d{1,3}){3}\b'
JSON & HTTP APIs
Intermediate · 8 min read
import json, requests
# JSON — serialisation
data = {'name': 'Aftab', 'scores': [95, 87]}
json_str = json.dumps(data, indent=2) # dict → JSON string
back = json.loads(json_str) # JSON string → dict
# HTTP with requests library
# pip install requests
# GET
r = requests.get(
'https://api.github.com/users/torvalds',
headers={'Accept': 'application/json'},
timeout=10
)
r.raise_for_status() # raise HTTPError if 4xx/5xx
user = r.json() # parse JSON response
# POST
r = requests.post(
'https://api.example.com/users',
json={'name': 'Aftab', 'email': 'a@b.com'},
headers={'Authorization': 'Bearer TOKEN'}
)
# Session (reuse TCP connection)
with requests.Session() as s:
s.headers.update({'Authorization': 'Bearer TOKEN'})
r1 = s.get('/api/profile')
r2 = s.get('/api/orders')
Async Python (asyncio)
Advanced · 10 min read
Python's asyncio enables concurrent I/O without threads. Ideal for API calls, database queries, and web servers (FastAPI, aiohttp).
import asyncio
# async def creates a coroutine
async def fetch_user(user_id):
await asyncio.sleep(1) # simulate I/O
return {'id': user_id, 'name': 'Aftab'}
# Run a single coroutine
user = asyncio.run(fetch_user(1))
# gather — run concurrently
async def main():
users = await asyncio.gather(
fetch_user(1),
fetch_user(2),
fetch_user(3)
)
print(users) # all 3 complete in ~1s, not 3s
asyncio.run(main())
# aiohttp for async HTTP
import aiohttp
async def get_data(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.json()
# asyncio Task — schedule without waiting
async def background():
task = asyncio.create_task(slow_operation())
await fast_operation() # runs while slow_op is pending
await task # wait for it here
Type Hints
Advanced · 8 min read
Python 3.5+ supports type hints — annotations that document expected types. They are checked by tools like mypy and Pyright, not at runtime.
from typing import Optional, Union, List, Dict, Tuple, Callable, Any
from __future__ import annotations # deferred evaluation (older Python)
# Function signatures
def greet(name: str, times: int = 1) -> str:
return (name + ' ') * times
# Modern generics (Python 3.9+)
def first(items: list[int]) -> int | None: # | for Union
return items[0] if items else None
# Type aliases
UserId = int
UserDict = dict[str, str | int]
# Callable types
def apply(func: Callable[[int], int], val: int) -> int:
return func(val)
# TypedDict — typed dictionaries
from typing import TypedDict
class User(TypedDict):
name: str
age: int
email: str
# dataclass — typed, auto-generates __init__, __repr__
from dataclasses import dataclass, field
@dataclass
class Product:
name: str
price: float
tags: list[str] = field(default_factory=list)
p = Product("Widget", 9.99)
print(p) # Product(name='Widget', price=9.99, tags=[])