Cross-Platform — Runs on Linux, Windows, macOS, embedded systems (MicroPython).
Dominant in AI/ML — De facto language for data science, machine learning, and automation.
Disadvantages
Slow Execution — Interpreted nature makes it slower than C, C++, or Java.
GIL (Global Interpreter Lock) — Limits true multi-threaded CPU-bound parallelism.
High Memory Usage — Dynamic typing and object overhead consume more RAM.
Not Ideal for Mobile — Limited native mobile development support.
Runtime Errors — Dynamic typing can cause type errors only caught at runtime.
Packaging Complexity — Dependency management (pip, venv, conda) can be tricky.
Basics
Hello World & Entry Point
# hello.pyprint("Hello, World!")# With main guard (best practice for scripts)def main(): print("Hello from main!")if __name__ == "__main__": main()
if __name__ == "__main__" ensures code only runs when the file is executed directly, not when imported.
Comments
# Single line comment"""Multi-line string used asa block comment (docstring style)"""def greet(name): """Greet a person by name. This is a proper docstring.""" print(f"Hello, {name}")
Variables & Data Types
age = 25 # intprice = 9.99 # floatname = "Alice" # stris_active = True # boolnothing = None # NoneType# Multiple assignmentx = y = z = 0a, b, c = 1, 2, 3 # tuple unpacking# Type checkingprint(type(age)) # <class 'int'>print(isinstance(age, int)) # True
Primitive Data Types Table
Type Example Notes
int 42, -7, 0 Arbitrary precision (no overflow)
float 3.14, -0.5 64-bit IEEE 754 double
complex 3+4j Real + imaginary
bool True, False Subclass of int (True==1, False==0)
str "hello", 'world' Immutable Unicode sequence
bytes b"data" Immutable byte sequence
NoneType None Represents absence of value
name = input("Enter your name: ") # always returns strage = int(input("Enter your age: ")) # convert to intprint(f"Hello {name}, you are {age} years old.")
match / case (Python 3.10+ — Structural Pattern Matching)
command = "quit"match command: case "quit": print("Quitting...") case "help": print("Showing help") case _: print("Unknown command")# Match with types and destructuringpoint = (1, 0)match point: case (0, 0): print("Origin") case (x, 0): print(f"On X-axis at {x}") case (0, y): print(f"On Y-axis at {y}") case (x, y): print(f"Point at ({x}, {y})")
Loops
# for loop — iterates over any iterablefor i in range(5): print(i) # 0 1 2 3 4# range(start, stop, step)for i in range(1, 10, 2): print(i) # 1 3 5 7 9# iterate over listfruits = ["apple", "banana", "cherry"]for fruit in fruits: print(fruit)# enumerate — get index + valuefor i, fruit in enumerate(fruits): print(f"{i}: {fruit}")# zip — iterate multiple iterables togethernames = ["Alice", "Bob"]scores = [95, 87]for name, score in zip(names, scores): print(f"{name}: {score}")# while loopn = 0while n < 5: print(n) n += 1# do-while equivalentwhile True: val = input("Enter 'q' to quit: ") if val == 'q': break
break / continue / pass / else on loops
for i in range(10): if i == 3: continue # skip 3 if i == 7: break # stop at 7 print(i)# Output: 0 1 2 4 5 6# pass — placeholder, does nothingdef todo(): pass # implement later# else on for/while — runs if loop completed without breakfor i in range(5): if i == 10: breakelse: print("Loop completed normally") # this runs
# A decorator wraps a function to add behaviordef logger(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") result = func(*args, **kwargs) print(f"Done {func.__name__}") return result return wrapper@loggerdef add(a, b): return a + badd(3, 4)# Calling add# Done add# Decorator with argumentsdef repeat(n): def decorator(func): def wrapper(*args, **kwargs): for _ in range(n): func(*args, **kwargs) return wrapper return decorator@repeat(3)def say_hi(): print("Hi!")say_hi() # Hi! Hi! Hi!# functools.wraps — preserves original function metadatafrom functools import wrapsdef timer(func): @wraps(func) def wrapper(*args, **kwargs): import time start = time.time() result = func(*args, **kwargs) print(f"{func.__name__} took {time.time()-start:.4f}s") return result return wrapper
Generators & yield
# Generator function — uses yield instead of returndef count_up(n): for i in range(n): yield i # pauses here, resumes on next()gen = count_up(5)print(next(gen)) # 0print(next(gen)) # 1for val in count_up(3): print(val) # 0 1 2# Generator expression (like list comp but lazy)squares = (x**2 for x in range(10))print(next(squares)) # 0print(sum(squares)) # 1+4+9+...+81 = 284# Infinite generatordef fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + bfib = fibonacci()print([next(fib) for _ in range(8)]) # [0, 1, 1, 2, 3, 5, 8, 13]# yield from — delegate to sub-generatordef chain(*iterables): for it in iterables: yield from itlist(chain([1, 2], [3, 4], [5])) # [1, 2, 3, 4, 5]
Data Structures
Lists
nums = [1, 2, 3, 4, 5]# Access & Slicingnums[0] # 1nums[-1] # 5nums[1:3] # [2, 3]nums[::-1] # [5, 4, 3, 2, 1] (reversed)# Mutatingnums.append(6) # [1, 2, 3, 4, 5, 6]nums.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6]nums.extend([7, 8]) # adds multiplenums.remove(3) # removes first occurrence of 3nums.pop() # removes & returns last elementnums.pop(0) # removes & returns element at index 0nums.sort() # sort in-placenums.sort(reverse=True) # descendingnums.reverse() # reverse in-placenums.clear() # empty the list# Infolen(nums) # lengthnums.count(2) # count occurrencesnums.index(4) # index of first occurrence2 in nums # membership test# Copyingcopy1 = nums.copy() # shallow copycopy2 = nums[:] # also shallow copyimport copydeep = copy.deepcopy(nums) # deep copy
List Comprehensions
# [expression for item in iterable if condition]squares = [x**2 for x in range(10)]# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]evens = [x for x in range(20) if x % 2 == 0]# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]# Nested comprehensionmatrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]# [[1, 2, 3], [2, 4, 6], [3, 6, 9]]# Flatten a 2D listflat = [x for row in matrix for x in row]# [1, 2, 3, 2, 4, 6, 3, 6, 9]# With conditional expressionlabels = ["even" if x % 2 == 0 else "odd" for x in range(5)]# ['even', 'odd', 'even', 'odd', 'even']
try: result = 10 / 0except ZeroDivisionError as e: print(f"Error: {e}")except (TypeError, ValueError) as e: print(f"Type or Value error: {e}")except Exception as e: print(f"Unexpected error: {e}")else: print("No error occurred") # runs only if no exceptionfinally: print("Always runs") # cleanup code# Catching all exceptions (use sparingly)try: risky_operation()except Exception: pass # silently ignore
Raising Exceptions
def divide(a, b): if b == 0: raise ValueError("Denominator cannot be zero") return a / b# Re-raisetry: divide(1, 0)except ValueError: print("Caught it") raise # re-raises the same exception# raise from — chain exceptionstry: int("abc")except ValueError as e: raise RuntimeError("Conversion failed") from e
Custom Exceptions
class AppError(Exception): """Base exception for this app.""" passclass ValidationError(AppError): def __init__(self, field, message): self.field = field super().__init__(f"Validation error on '{field}': {message}")class NotFoundError(AppError): passtry: raise ValidationError("email", "Invalid format")except ValidationError as e: print(e) # Validation error on 'email': Invalid format print(e.field) # email
Context Managers (with statement)
# Built-in: file handlingwith open("data.txt", "r") as f: content = f.read()# file automatically closed after block, even on exception# Custom context manager using classclass Timer: def __enter__(self): import time self.start = time.time() return self def __exit__(self, exc_type, exc_val, exc_tb): import time self.elapsed = time.time() - self.start print(f"Elapsed: {self.elapsed:.4f}s") return False # don't suppress exceptionswith Timer() as t: sum(range(1_000_000))# Custom context manager using contextlibfrom contextlib import contextmanager@contextmanagerdef managed_resource(name): print(f"Acquiring {name}") try: yield name finally: print(f"Releasing {name}")with managed_resource("DB connection") as res: print(f"Using {res}")
File I/O
Reading & Writing Files
# Write to filewith open("output.txt", "w") as f: f.write("Hello, World!\n") f.writelines(["Line 1\n", "Line 2\n"])# Read entire filewith open("output.txt", "r") as f: content = f.read()# Read line by line (memory efficient)with open("output.txt", "r") as f: for line in f: print(line.strip())# Read all lines into listwith open("output.txt", "r") as f: lines = f.readlines()# Append to filewith open("output.txt", "a") as f: f.write("Appended line\n")# File modes:# "r" — read (default)# "w" — write (overwrites)# "a" — append# "rb" — read binary# "wb" — write binary# "r+" — read + write
nums = [1, 2, 3, 4, 5]# map — apply function to each elementsquares = list(map(lambda x: x**2, nums))# [1, 4, 9, 16, 25]# filter — keep elements where function returns Trueevens = list(filter(lambda x: x % 2 == 0, nums))# [2, 4]# reduce — fold list to single valuefrom functools import reduceproduct = reduce(lambda acc, x: acc * x, nums)# 120 (1*2*3*4*5)# Prefer list comprehensions over map/filter for readabilitysquares = [x**2 for x in nums]evens = [x for x in nums if x % 2 == 0]
import asyncioasync def task(name, delay): await asyncio.sleep(delay) print(f"{name} done after {delay}s") return nameasync def main(): # Run tasks concurrently (not sequentially) results = await asyncio.gather( task("A", 2), task("B", 1), task("C", 3), ) print(results) # ['A', 'B', 'C']asyncio.run(main())# B done after 1s# A done after 2s# C done after 3s# Total time: ~3s (not 6s)# asyncio.create_task — fire and forgetasync def main(): t1 = asyncio.create_task(task("X", 1)) t2 = asyncio.create_task(task("Y", 2)) await t1 await t2
Async Generators & Context Managers
# Async generatorasync def async_range(n): for i in range(n): await asyncio.sleep(0.1) yield iasync def main(): async for val in async_range(5): print(val)# Async context managerclass AsyncDB: async def __aenter__(self): print("Connecting...") return self async def __aexit__(self, *args): print("Disconnecting...")async def main(): async with AsyncDB() as db: print("Using DB")
Modules & Packages
Importing
import math # import moduleimport math as m # aliasfrom math import sqrt, pi # import specific namesfrom math import * # import all (avoid — pollutes namespace)print(math.sqrt(16)) # 4.0print(m.pi) # 3.14159...print(sqrt(25)) # 5.0# Relative imports (inside packages)from . import sibling_modulefrom ..utils import helper
Creating Modules & Packages
mypackage/
├── __init__.py # makes it a package
├── utils.py
└── models/
├── __init__.py
└── user.py
# utils.pydef add(a, b): return a + b# __init__.py — control what's exportedfrom .utils import add__all__ = ["add"]# Usagefrom mypackage import addfrom mypackage.models.user import User
Standard Library Highlights
import os # OS interface, env vars, pathsimport sys # interpreter info, argv, pathimport json # JSON encode/decodeimport re # regular expressionsimport datetime # dates and timesimport math # math functionsimport random # random numbersimport collections # Counter, defaultdict, deque, OrderedDictimport itertools # combinatorics, infinite iteratorsimport functools # higher-order functions, lru_cacheimport threading # threadsimport multiprocessing # processesimport subprocess # run shell commandsimport logging # logging frameworkimport unittest # testingimport argparse # CLI argument parsingimport dataclasses # @dataclassimport typing # type hintsimport pathlib # modern file pathsimport contextlib # context manager utilitiesimport abc # abstract base classesimport enum # enumerationsimport copy # shallow/deep copyimport time # time functionsimport hashlib # hashing (SHA, MD5)import base64 # base64 encodingimport csv # CSV read/writeimport sqlite3 # SQLite databaseimport http.server # simple HTTP serverimport urllib # URL handling
Advanced Python
Metaclasses
# Metaclass controls class creationclass SingletonMeta(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls]class Database(metaclass=SingletonMeta): def __init__(self): self.connection = "Connected"db1 = Database()db2 = Database()print(db1 is db2) # True — same instance
Descriptors
# Descriptors control attribute accessclass Validator: def __set_name__(self, owner, name): self.name = name def __get__(self, obj, objtype=None): if obj is None: return self return obj.__dict__.get(self.name) def __set__(self, obj, value): if not isinstance(value, int) or value < 0: raise ValueError(f"{self.name} must be a non-negative int") obj.__dict__[self.name] = valueclass Product: price = Validator() quantity = Validator() def __init__(self, price, quantity): self.price = price self.quantity = quantityp = Product(10, 5)# p.price = -1 # ValueError
slots — Memory Optimization
# Without __slots__: each instance has a __dict__ (flexible but heavy)# With __slots__: fixed set of attributes, no __dict__, less memoryclass Point: __slots__ = ("x", "y") def __init__(self, x, y): self.x = x self.y = yp = Point(1, 2)print(p.x) # 1# p.z = 3 # AttributeError — z not in __slots__# p.__dict__ # AttributeError — no __dict__# Useful when creating millions of small objects
Enums
from enum import Enum, auto, IntEnumclass Color(Enum): RED = 1 GREEN = 2 BLUE = 3class Direction(Enum): NORTH = auto() # auto-assigns values: 1, 2, 3, 4 SOUTH = auto() EAST = auto() WEST = auto()print(Color.RED) # Color.REDprint(Color.RED.value) # 1print(Color.RED.name) # "RED"print(Color(2)) # Color.GREENfor c in Color: print(c)# IntEnum — can compare with intsclass Status(IntEnum): OK = 200 NOT_FOUND = 404 ERROR = 500print(Status.OK == 200) # True
import threadingdef worker(name, count): for i in range(count): print(f"{name}: {i}")t1 = threading.Thread(target=worker, args=("Thread-1", 3))t2 = threading.Thread(target=worker, args=("Thread-2", 3))t1.start()t2.start()t1.join() # wait for t1 to finisht2.join()# Thread-safe with Locklock = threading.Lock()counter = 0def increment(): global counter with lock: counter += 1# Note: GIL limits CPU-bound threading — use multiprocessing for CPU tasks
Multiprocessing
from multiprocessing import Process, Pooldef cpu_task(n): return sum(i**2 for i in range(n))# Process pool — parallel executionwith Pool(processes=4) as pool: results = pool.map(cpu_task, [10**6, 10**6, 10**6, 10**6])print(results)# Individual processesp = Process(target=cpu_task, args=(10**6,))p.start()p.join()
concurrent.futures — High-Level API
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutordef fetch(url): import time; time.sleep(1) return f"Result from {url}"urls = ["url1", "url2", "url3", "url4"]# ThreadPoolExecutor — good for I/O-bound taskswith ThreadPoolExecutor(max_workers=4) as executor: results = list(executor.map(fetch, urls))print(results)# ProcessPoolExecutor — good for CPU-bound taskswith ProcessPoolExecutor(max_workers=4) as executor: futures = [executor.submit(cpu_task, 10**6) for _ in range(4)] results = [f.result() for f in futures]
Libs & Framework
1. Web Development
Django: High-level web framework for building robust web applications.
Flask: Lightweight web framework for small to medium web apps.
FastAPI: Modern, fast (high-performance) web framework for building APIs.
2. Data Science & Machine Learning
NumPy: Fundamental package for scientific computing (handling arrays and matrices).
Pandas: Library for data manipulation and analysis, especially for structured data.
Matplotlib: Plotting library for creating static, animated, and interactive visualizations.
Scikit-learn: Machine learning library for data mining and analysis.
PyTorch: Deep learning library for building neural networks.
XGBoost: Optimized gradient boosting library for machine learning.
3. Data Visualization
Seaborn: Statistical data visualization based on Matplotlib.
Plotly: Interactive graphing library for making interactive plots.
Bokeh: Interactive visualization library for web applications.