Types, comprehensions, OOP, async, decorators & stdlib essentials
Immutable: int, float, str, bool, tuple, frozenset. Mutable: list, dict, set. None is a singleton null type. Everything in Python is an object.
x = 42 # int
pi = 3.14 # float
name = 'Alice' # str
flag = True # bool
nums = [1,2,3] # list (mutable)
coords = (1,2) # tuple (immutable)
uniq = {1,2,3} # set
data = {'a': 1} # dict
nothing = None # NoneTypeConcise syntax for creating lists from iterables. Supports filtering with if, nested loops, and expressions. Faster than equivalent for loops.
# Basic
squares = [x**2 for x in range(10)]
# With filter
evens = [x for x in range(20) if x % 2 == 0]
# With transform + filter
names = [name.upper() for name in users if name.startswith('S')]
# Dict comprehension
word_len = {w: len(w) for w in words}
# Set comprehension
unique_lengths = {len(w) for w in words}*args collects positional arguments into a tuple. **kwargs collects keyword arguments into a dict. Use for flexible function signatures. * and ** also unpack in function calls.
def greet(*args, **kwargs):
for name in args:
print(f"Hi {name}")
for key, val in kwargs.items():
print(f"{key}: {val}")
greet('Alice', 'Alex', role='dev', lang='Python')
# Unpacking
nums = [1, 2, 3]
print(*nums) # 1 2 3
config = {'host': 'localhost', 'port': 8080}
connect(**config)Decorators wrap functions to add behavior. They take a function, return a new function. Use @decorator syntax. Common uses: logging, timing, auth, caching.
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time()-start:.2f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return 'done'Classes define objects with __init__ constructor, self reference, methods, and class/static methods. Supports inheritance, multiple inheritance, and dunder methods for operator overloading.
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def greet(self):
return f"Hi, I'm {self.name}"
def __repr__(self):
return f"User({self.name!r}, {self.email!r})"
def __eq__(self, other):
return self.email == other.email
class Admin(User):
def __init__(self, name, email, level):
super().__init__(name, email)
self.level = levelGenerators produce values lazily with yield. They're memory-efficient for large sequences. Generator expressions use () instead of []. They implement the iterator protocol.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Lazy โ only computes on demand
fib = fibonacci()
for _ in range(10):
print(next(fib))
# Generator expression
sum_sq = sum(x**2 for x in range(1000000))
# Memory: O(1) vs list comprehension O(n)try/except catches exceptions. else runs if no exception. finally always runs. Raise custom exceptions with raise. Create custom exceptions by inheriting Exception.
try:
result = 10 / x
except ZeroDivisionError:
print("Can't divide by zero")
except (TypeError, ValueError) as e:
print(f"Bad input: {e}")
else:
print(f"Result: {result}") # no exception
finally:
cleanup() # always runs
# Custom exception
class ValidationError(Exception):
def __init__(self, field, message):
self.field = field
super().__init__(message)Context managers handle setup/teardown automatically. with statement ensures cleanup even if exceptions occur. Built-in: files, locks, DB connections. Create with __enter__/__exit__ or @contextmanager.
# Built-in file context manager
with open('data.txt', 'r') as f:
content = f.read()
# File auto-closed, even on exception
# Custom context manager
from contextlib import contextmanager
@contextmanager
def timer(label):
start = time.time()
yield # code in 'with' block runs here
print(f"{label}: {time.time()-start:.2f}s")
with timer('processing'):
heavy_computation()Hash map with O(1) average lookup. Keys must be hashable (immutable). Ordered since Python 3.7. Methods: get(), items(), keys(), values(), setdefault(), update().
d = {'name': 'Alice', 'role': 'dev'}
# Safe access with default
age = d.get('age', 0) # 0 if missing
# Merge (Python 3.9+)
merged = d | {'age': 30, 'lang': 'Python'}
# Iteration
for key, value in d.items():
print(f"{key}: {value}")
# defaultdict for counting
from collections import defaultdict
counts = defaultdict(int)
for word in words:
counts[word] += 1f-strings (Python 3.6+) embed expressions in strings with {}. Support format specs, method calls, and expressions. The modern replacement for .format() and % formatting.
name = 'Alice'
age = 30
# f-string (preferred)
print(f"{name} is {age} years old")
print(f"Pi: {3.14159:.2f}") # 3.14
print(f"{'hello':>20}") # right-align
print(f"{1000000:,}") # 1,000,000
print(f"{name!r}") # repr: 'Alice'
# Debug (Python 3.8+)
print(f"{name=}, {age=}") # name='Alice', age=30Type hints add optional type annotations. Not enforced at runtime โ use mypy for static checking. Improves IDE support, documentation, and code clarity.
from typing import Optional, Union
def greet(name: str, times: int = 1) -> str:
return f"Hello {name}! " * times
def find_user(user_id: int) -> Optional[dict]:
return db.get(user_id) # might return None
# Modern syntax (Python 3.10+)
def process(data: str | int) -> list[str]:
...
# Generics
def first(items: list[T]) -> T:
return items[0]== compares values (equality). is compares identity (same object in memory). Use is only for None, True, False checks. Small integers (-5 to 256) are cached so is may be misleading.
a = [1, 2, 3]
b = [1, 2, 3]
a == b # True (same value)
a is b # False (different objects)
# Correct None check
if result is None:
print("No result")
# NOT: if result == None (works but wrong style)
# Integer caching gotcha
x = 256
y = 256
x is y # True (cached)
x = 257
y = 257
x is y # May be False!Use open() with context managers. Modes: r (read), w (write/truncate), a (append), x (create new), b (binary). pathlib is the modern way to handle paths.
from pathlib import Path
# Read
text = Path('data.txt').read_text()
# Write
Path('output.txt').write_text('hello')
# Line by line (memory efficient)
with open('big.txt') as f:
for line in f:
process(line.strip())
# JSON
import json
data = json.loads(Path('config.json').read_text())
Path('out.json').write_text(json.dumps(data, indent=2))dataclasses auto-generate __init__, __repr__, __eq__, and more. Clean alternative to regular classes for data containers. Use frozen=True for immutable instances.
from dataclasses import dataclass, field
@dataclass
class User:
name: str
email: str
age: int = 0
tags: list[str] = field(default_factory=list)
user = User('Alice', '[email protected]', 30)
print(user) # User(name='Alice', email='[email protected]', age=30, tags=[])
@dataclass(frozen=True) # immutable
class Point:
x: float
y: floatmap() applies function to each item. filter() keeps items where function returns True. sorted() with key function. reduce() accumulates values. Lambda for inline functions.
nums = [1, 2, 3, 4, 5]
# map โ transform
squared = list(map(lambda x: x**2, nums))
# filter โ select
evens = list(filter(lambda x: x % 2 == 0, nums))
# sorted with key
users = sorted(users, key=lambda u: u.age, reverse=True)
# reduce โ accumulate
from functools import reduce
total = reduce(lambda a, b: a + b, nums) # 15
# Prefer comprehensions over map/filter:
[x**2 for x in nums] # cleaner than mapimport loads modules. from...import gets specific names. Packages are directories with __init__.py. Use relative imports within packages. __all__ controls * imports.
import os
import json
from pathlib import Path
from collections import defaultdict, Counter
from typing import Optional
# Alias
import numpy as np
import pandas as pd
# Relative import (within package)
from . import utils
from ..models import User
# Conditional import
try:
import ujson as json
except ImportError:
import jsonThe GIL allows only one thread to execute Python bytecode at a time. Limits CPU-bound parallelism with threads. Workarounds: multiprocessing (separate processes), C extensions, or async for I/O-bound work.
# Threads โ good for I/O-bound (GIL released during I/O)
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=5) as pool:
results = pool.map(fetch_url, urls)
# Processes โ good for CPU-bound (bypasses GIL)
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
results = pool.map(compute_heavy, data)
# Async โ best for I/O-bound, single thread
async def main():
results = await asyncio.gather(*tasks)async def defines a coroutine. await pauses until the result is ready. asyncio.run() starts the event loop. Use asyncio.gather() for concurrent I/O. Single-threaded concurrency.
import asyncio
import aiohttp
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
async def main():
urls = ['https://api.example.com/1', '/2', '/3']
results = await asyncio.gather(
*[fetch(url) for url in urls]
)
return results
asyncio.run(main())Assignment expression (Python 3.8+) assigns and returns a value in one step. Useful in while loops, comprehensions, and if statements to avoid repeated computation.
# Without walrus
line = input()
while line != 'quit':
process(line)
line = input()
# With walrus
while (line := input()) != 'quit':
process(line)
# In comprehension
results = [y for x in data if (y := expensive(x)) > threshold]
# In if statement
if (match := pattern.search(text)) is not None:
print(match.group())os/pathlib (filesystem), json (serialization), datetime (time), re (regex), collections (data structures), itertools (iteration), functools (HOFs), logging, unittest, typing.
from pathlib import Path # modern file paths
from collections import Counter, deque, defaultdict
from itertools import chain, groupby, product
from functools import lru_cache, partial, reduce
from datetime import datetime, timedelta
from typing import Optional, Union
import re, json, os, logging, unittest
# Caching
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2: return n
return fibonacci(n-1) + fibonacci(n-2)