Complete Reference Guide

Python Cheatsheet

Every syntax, method, pattern, and idiom β€” organized for rapid lookup and deep understanding

26 Topic Sections 500+ Code Examples 3.12+ Compatible All Built-ins Covered
🌍

Environments & Isolation

Overview & "Why Isolate?"
  • Dependency isolation: Each project gets its own site-packages to prevent conflicts.
  • Reproducibility: Environments can be recreated precisely using lock files.
  • Version separation: Different projects can use different Python versions (3.10 vs 3.12).
  • No system pollution: Keeps global system Python tools safe from conflicting updates.

Available Management Tools:
  • venv - built-in, lightweight, standard library
  • virtualenv - faster, supports older Python versions
  • conda - cross-language, binary packages (Anaconda)
  • pipenv - official PyPA tool (Pipfile)
  • poetry / pdm - modern pyproject.toml based workflows
  • pyenv - manage multiple Python versions globally
  • Docker - full OS-level isolation
Built-in venv Module
# 1. Create environment
python -m venv myenv

# 2. Activate (Linux/macOS)
source myenv/bin/activate

# 2. Activate (Windows)
myenv\Scripts\activate       # CMD
.\myenv\Scripts\Activate.ps1 # PowerShell

# 3. Install packages (local to env)
pip install requests

# 4. Deactivate
deactivate
  • Pitfall: Moving or renaming a venv directory breaks it due to hardcoded paths. Recreate instead.
  • Pitfall: The environment is bound to the exact Python version that created it.
virtualenv & Conda
# --- virtualenv --- (predecessor to venv)
pip install virtualenv
virtualenv -p /usr/bin/python3.11 myenv
source myenv/bin/activate
# (Faster creation, works offline, supports Python 2)

# --- Conda (Anaconda / Miniconda) ---
conda create -n myenv python=3.11
conda activate myenv
conda install numpy pandas
conda env export --no-builds > environment.yml
conda deactivate
  • Conda Advantage: Manages non-Python libraries (C libs) effortlessly. Great for Data Science/HPC.
  • Conda Pitfall: Mixing conda and pip can break environment consistency. Environments are typically stored centrally, not in the project folder.
Poetry & PDM (pyproject.toml)
# --- Poetry ---
poetry new myapp
cd myapp
poetry add flask            # resolves dependencies & updates lock
poetry add --group dev pytest
poetry run python main.py
poetry shell                # spawn subshell with env

# --- PDM ---
pdm init
pdm add requests
pdm run python script.py    # No activation needed
  • Advantage: Uses modern standards (PEP-621/PEP-582). Built-in robust lock files for exact reproducibility. Replaces requirements.txt.
pyenv & Docker
# --- pyenv (manage multiple Py versions) ---
pyenv install 3.12.4
pyenv virtualenv 3.12.4 myproject
pyenv activate myproject
# Auto-switch: echo "myproject" > .python-version

# --- Docker (OS-level isolation) ---
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
  • Docker: Complete OS-stack portability. Essential for CI/CD and deployments.
General Pitfalls & Workflows
  • Forgetting to activate: Running python without activation uses global packages!
  • Moving directories: Virtual environments have hardcoded absolute paths. Never move them; recreate them.
  • Using sudo with pip: Never use sudo inside a venv. It breaks permissions or installs globally.
  • Lock file staleness: In Poetry/Pipenv/PDM, always update and commit the lock file.
  • IDE Managed Envs: VS Code/PyCharm abstract away venv creation. Always keep a requirements.txt or pyproject.toml in version control!
πŸš€

Getting Started

Installation
  • Windows: python.org/downloads β†’ check "Add to PATH"
  • macOS: brew install python3
  • Linux: sudo apt install python3
# Check version
$ python --version
$ python3 --version

# Start interactive REPL
$ python3
>>> exit()  # or Ctrl+D
Running Python
$ python script.py          # run file
$ python3 -i script.py     # run, drop to REPL
$ python -c 'print(42)'    # one-liner
$ python -m module_name    # run as module
$ python -m http.server    # common example
$ python -m pytest         # run pytest

# REPL special vars
>>> _           # last result
>>> help(str)   # built-in help
>>> dir(list)   # attributes/methods
Script Entry Point
#!/usr/bin/env python3  # shebang (Unix)
# -*- coding: utf-8 -*-  # encoding

def main():
    print("Hello, World!")

if __name__ == '__main__':
    main()  # only when executed directly
            # NOT when imported
Comments & Docstrings
# Single-line comment
x = 5  # Inline comment

"""Module-level docstring."""

def greet(name: str) -> str:
    """
    Return greeting for name.

    Args:
        name (str): Person's name.
    Returns:
        str: Greeting string.
    Examples:
        >>> greet('Alice')
        'Hello, Alice!'
    """
    return f'Hello, {name}!'

print(greet.__doc__)   # access docstring
Beginner Pitfalls
# --- Context Setup ---
undef_var = None
x = 0
# ---------------------

# 1. Shadowing built-ins
list = [1, 2]     # BAD! overwrites built-in list()
del list          # Fix: unbind the name

# 2. Indentation errors
if True:
    print("bad")      # IndentationError

# 3. NameError (used before assigned)
print(undef_var)  # NameError

# 4. = vs ==
# if x = 5:       # SyntaxError (assignment in if)
if x == 5: pass   # Correct (comparison)
πŸ”’

Data Types & Variables

Type Overview
TypeExampleMutable
int42, -7, 0b1111No
float3.14, 1.5e-3No
complex3+4jNo
str'hello', "world"No
bytesb'data'No
boolTrue, FalseNo
NoneTypeNoneβ€”
list[1, 2, 3]Yes
dict{'a': 1}Yes
set{1, 2, 3}Yes
bytearraybytearray(b'hi')Yes
tuple(1, 2, 3)No
frozensetfrozenset({1,2})No
Type Inspection
type(42)             # <class 'int'>
type(3.14)           # <class 'float'>
type('hi')           # <class 'str'>
type(True)           # <class 'bool'>
type(None)           # <class 'NoneType'>

isinstance(3, int)          # True
isinstance(True, int)        # True (bool IS int!)
isinstance(3, (int, float))  # True (tuple check)
issubclass(bool, int)       # True

id(42)       # unique memory address (int)
hash(42)     # hash value (for hashable types)
Type Conversion
int('42')            # 42
int(3.9)             # 3  (truncates!)
int('ff', 16)        # 255 (base-16)
int('0b1111', 0)     # 15  (auto-detect)
float('3.14')        # 3.14
float('inf')         # inf
str(42)              # '42'
bool(0)              # False
bool('')             # False
bool([])             # False
bool(None)           # False
bytes('hi', 'utf-8') # b'hi'
list('abc')          # ['a','b','c']
tuple([1,2,3])       # (1, 2, 3)
set([1,1,2,3])       # {1, 2, 3}
dict(a=1, b=2)        # {'a':1,'b':2}
frozenset({1,2})     # frozenset({1,2})
complex(3, 4)         # (3+4j)
Variables & Assignment
# --- Context Setup ---
data = None
import tempfile
f = tempfile.TemporaryFile(mode="w+")
def process(x): return x
# ---------------------

# Basic assignment
x = 42
name, age = 'Alice', 30   # tuple unpack
a = b = c = 0              # chained

# Extended unpacking
first, *rest = [1,2,3,4]   # rest=[2,3,4]
*init, last = [1,2,3,4]   # init=[1,2,3]
a, *mid, z = [1,2,3,4,5]  # mid=[2,3,4]

# Walrus operator := (3.8+)
if (n := len(data)) > 10:
    print(f'{n} items')
# Use in while loops
while chunk := f.read(8192):
    process(chunk)

# Augmented assignment
x += 1;  x -= 1;  x *= 2;  x /= 2
x //= 3; x %= 5;  x **= 2
x &= 0b1111; x |= 0b0001; x ^= 1
x >>= 2;  x <<= 1

del x    # unbinds name from object
Naming & Scope (LEGB)
  • snake_case β†’ variables, functions
  • PascalCase β†’ classes
  • UPPER_SNAKE β†’ constants
  • _private β†’ convention-private
  • __mangled β†’ name mangling in class
  • __dunder__ β†’ magic/special methods
# LEGB: Local β†’ Enclosing β†’ Global β†’ Built-in
x = 'global'

def outer():
    x = 'enclosing'
    def inner():
        x = 'local'
        print(x)    # 'local'
    inner()
    print(x)        # 'enclosing'

count = 0
def inc():
    global count   # modify global var
    count += 1

def make_adder(n):
    def adder(x):
        nonlocal n  # modify enclosing
        n += x; return n
    return adder
Mutability & Pitfalls
  • Mutable: list, dict, set, bytearray
  • Immutable: int, float, str, tuple, frozenset, bool
# Pitfall: Chained assignment with mutables
a = b = []
a.append(1)
print(b)  # Output: [1] (both point to same list)

# Pitfall: input() always returns str
age = int(input("Age: "))  # MUST convert

# ValueError: float string to int directly fails
# x = int('4.5')  # ValueError
x = int(float('4.5'))  # Correct

# Truthy surprises
bool([])   # False (empty)
bool([0])  # True (non-empty)
bool("0")  # True (non-empty string)
πŸ“

Numbers & Math

Integer Literals & Bases
255          # decimal
0b11111111   # binary β†’ 255
0o377        # octal  β†’ 255
0xFF         # hex    β†’ 255
1_000_000    # underscores (readability)

bin(255)     # '0b11111111'
oct(255)     # '0o377'
hex(255)     # '0xff'
int('ff', 16) # 255
int('111', 2) # 7

# Bit operations
5 & 3   # 1 (AND)
5 | 3   # 7 (OR)
5 ^ 3   # 6 (XOR)
~5      # -6 (NOT, bitwise complement)
5 << 1  # 10 (left shift Γ— 2)
5 >> 1  # 2  (right shift Γ· 2)
(255).bit_length()  # 8
(7).bit_count()     # 3  (3.10+)
Float & Complex
# Float literals
3.14;  1.5e10;  1.5e-3
float('inf')    # infinity
float('-inf')   # negative infinity
float('nan')    # Not a Number

import math
math.isnan(float('nan'))  # True
math.isinf(float('inf'))  # True
math.isfinite(3.14)       # True

# Precision issues
0.1 + 0.2   # 0.30000000000000004 !
round(0.1+0.2, 10)  # 0.3

from decimal import Decimal
Decimal('0.1') + Decimal('0.2')  # 0.3 exact

# Complex numbers
z = 3 + 4j
z.real        # 3.0
z.imag        # 4.0
z.conjugate() # (3-4j)
abs(z)        # 5.0 (magnitude)
complex(3, 4) # (3+4j)
Arithmetic & Precedence
10 + 3   # 13    addition
10 - 3   # 7     subtraction
10 * 3   # 30    multiplication
10 / 3   # 3.333 true division (always float)
10 // 3  # 3     floor division (int)
10 % 3   # 1     modulo
2 ** 10  # 1024  exponentiation
divmod(17, 5)      # (3, 2)
pow(2, 10)         # 1024
pow(2, 10, 1000)   # 24  (modular pow)
abs(-42)           # 42
round(3.14159, 2)  # 3.14
Precedence (high β†’ low)
  • () parentheses
  • ** exponentiation (right-assoc)
  • +x -x ~x unary
  • * / // % multiply/divide
  • + - add/subtract
  • << >> bit shifts
  • & ^ | bitwise (in order)
  • == != < > <= >= comparisons
  • not and or logical
  • := walrus (lowest)
math module
import math
math.pi       # 3.14159265…
math.e        # 2.71828182…
math.tau      # 6.28318530…
math.inf      # float('inf')
math.nan      # float('nan')

math.sqrt(16)       # 4.0
math.ceil(3.2)      # 4
math.floor(3.9)     # 3
math.trunc(3.9)     # 3
math.factorial(5)   # 120
math.gcd(12, 8)     # 4
math.lcm(4, 6)      # 12  (3.9+)
math.log(math.e)    # 1.0
math.log(8, 2)      # 3.0
math.log2(8)        # 3.0
math.log10(1000)   # 3.0
math.exp(1)         # 2.71828…
math.sin(math.pi/2) # 1.0
math.cos(0)         # 1.0
math.degrees(math.pi) # 180.0
math.radians(180)  # 3.14159…
math.hypot(3, 4)    # 5.0
math.comb(5, 2)     # 10 (combinations)
math.perm(5, 2)     # 20 (permutations)
math.prod([1,2,3,4]) # 24  (3.8+)
random & statistics
import random
random.seed(42)          # reproducible
random.random()           # 0.0 – 1.0
random.uniform(1.0, 5.0)  # float in range
random.randint(1, 6)       # int inclusive
random.randrange(0,10,2)   # even 0–8
random.choice([1,2,3])    # pick one
random.choices([1,2,3], k=5) # w/ replacement
random.sample([1,2,3,4], 2) # no replacement
lst = [1,2,3]; random.shuffle(lst)

import statistics as st
data = [1, 2, 3, 4, 5, 5]
st.mean(data)      # 3.333
st.median(data)    # 3.5
st.mode(data)      # 5
st.stdev(data)     # 1.505  (sample)
st.variance(data)  # 2.266  (sample)
st.pstdev(data)    # population stdev
st.pvariance(data) # population variance
st.quantiles(data, n=4)  # quartiles
πŸ”€

Strings

Creation & Slicing
# --- Context Setup ---
name = "name"
# ---------------------

s1 = 'single';  s2 = "double"
s3 = '''multi
line'''                  # triple quotes
raw = r'C:\Users\no\escape'  # raw string
byt = b'bytes literal'
fs  = f'Hello {name}'        # f-string

# Escape sequences
# \n  \t  \\  \'  \"  \r  \0  \xNN  \uNNNN

# Indexing & Slicing
s = 'Python Basics'
s[0]      # 'P' (first)
s[-1]     # 's' (last)
s[0:6]    # 'Python'
s[7:]     # 'Basics'
s[::2]    # every 2nd char
s[::-1]   # reversed
s[1:10:2]  # step of 2
len(s)    # 13
sl = slice(0,6); s[sl]  # 'Python'
Case & Search Methods
# Case
'hello'.upper()       # 'HELLO'
'HELLO'.lower()       # 'hello'
'hello world'.title() # 'Hello World'
'hello'.capitalize()  # 'Hello'
'Hello'.swapcase()    # 'hELLO'
'Straße'.casefold()   # 'strasse' (unicode)

# Search
t = 'Python is great'
t.find('is')          # 7 (-1 if not found)
t.rfind('is')         # 7 (from right)
t.index('is')         # 7 (raises ValueError)
t.count('t')          # 2
t.startswith('Py')    # True
t.endswith('at')      # True
t.startswith(('Py','py')) # True (tuple)
'py' in t             # False (case-sensitive)
Modify, Split & Join
# Strip & Replace
' hello '.strip()        # 'hello'
' hello '.lstrip()       # 'hello '
' hello '.rstrip()       # ' hello'
'***hi***'.strip('*')   # 'hi'
'hello'.replace('l','L')    # 'heLLo'
'hello'.replace('l','L',1)  # 'heLlo'
'42'.zfill(6)            # '000042'
'-7'.zfill(6)            # '-00007'
'1\t2'.expandtabs(4)    # '1   2'

# Split & Join
'a,b,c'.split(',')       # ['a','b','c']
'a b c'.split()          # ['a','b','c']
'a b c'.split(' ', 1)   # ['a','b c']
'a b c'.rsplit(' ', 1)  # ['a b','c']
'a\nb\nc'.splitlines()   # ['a','b','c']
','.join(['a','b','c'])  # 'a,b,c'
'key=val'.partition('=')# ('key','=','val')
Alignment & Predicates
# Alignment & Padding
'Hi'.center(10)        # '    Hi    '
'Hi'.center(10, '*')   # '****Hi****'
'Hi'.ljust(10, '-')    # 'Hi--------'
'Hi'.rjust(10, '-')    # '--------Hi'

# Is* predicates
'abc'.isalpha()         # True
'123'.isdigit()         # True
'abc123'.isalnum()      # True
' '.isspace()           # True
'Hello World'.istitle() # True
'HELLO'.isupper()       # True
'hello'.islower()       # True
'var_1'.isidentifier()  # True
'hello'.isascii()       # True
'Β²'.isnumeric()         # True (unicode)
'Β²'.isdecimal()         # False
'hello'.isprintable()   # True

# Encode / Decode
'Hello'.encode('utf-8')  # b'Hello'
b'Hello'.decode('utf-8') # 'Hello'

# Translate
tbl = str.maketrans('aeiou', '*****')
'hello'.translate(tbl)   # 'h*ll*'
Pitfalls & Edge Cases
# --- Context Setup ---
words = ["hello", "world"]
# ---------------------

# 1. Strings are immutable! Methods return NEW strings
s = 'hello'
s.upper()       # s is still 'hello'
s = s.upper()   # Reassign to keep the change: 'HELLO'

# 2. Inefficient concatenation in a loop
for w in words:
    result += w      # BAD! O(n^2) time complexity
result = ''.join(words) # GOOD! O(n)

# 3. find() vs index()
'hello'.find('z')    # Returns -1
'hello'.index('z')   # Raises ValueError!
✍️

String Formatting

f-Strings (Recommended, 3.6+)
name='Alice'; score=95.678; n=1234567

f'Hello {name}'         # Hello Alice
f'{score:.2f}'           # 95.68
f'{score:8.2f}'          # '   95.68'
f'{n:,}'                 # 1,234,567
f'{n:#010x}'             # 0x0012d687
f'{name!r}'              # "'Alice'"  (repr)
f'{name!s}'              # 'Alice'    (str)
f'{name!a}'              # 'Alice'    (ascii)
f'{score=}'              # score=95.678 (3.8+)
f'{2+2}'                 # any expression!

# Alignment
f'{name:<10}'  # 'Alice     ' (left)
f'{name:>10}'  # '     Alice' (right)
f'{name:^10}'  # '  Alice   ' (center)
f'{name:*^10}' # '**Alice***' (fill char)
f'{n:010}'     # '0001234567' (zero-pad)

# Format types
f'{0.5:%}'     # '50.000000%'
f'{65:c}'      # 'A'  (unicode char)
f'{255:b}'     # '11111111' (binary)
f'{255:o}'     # '377' (octal)
f'{255:x}'     # 'ff'  (hex lower)
f'{255:X}'     # 'FF'  (hex upper)
f'{3.14:e}'    # '3.140000e+00'
f'{3.14:g}'    # '3.14' (general)
Format Spec Mini-Language
[[fill]align][sign][z][#][0][width][grouping][.precision][type]
SpecMeaning
<Left align
>Right align (default numbers)
^Center
=Pad after sign
+Always show sign
-Show minus only
spaceSpace for positive
#Prefix: 0b/0o/0x
,Thousands separator
_Underscore separator
.nPrecision digits
d b o x XInteger formats
e E f F g GFloat formats
%Percentage
s c nString/char/locale
.format() & % Style
# --- Context Setup ---
d = {"a": 1, "b": 2}
obj = {"key": "value"}
# ---------------------

# .format() method
'{} {}'.format('a', 'b')         # 'a b'
'{0} {1} {0}'.format('a', 'b')   # 'a b a'
'{name}'.format(name='Alice')   # 'Alice'
'{:.2f}'.format(3.14159)        # '3.14'
'{:>10}'.format('hi')           # '        hi'
'{0.name}'.format(obj)          # attr access
'{0[key]}'.format(d)            # item access

# % operator (legacy)
'%s is %d' % ('Alice', 30)     # 'Alice is 30'
'%.2f' % 3.14159                # '3.14'
'%10s' % 'hi'                   # '        hi'
'%r' % 'hello'                  # "'hello'"
'%x' % 255                      # 'ff'
'%o' % 8                        # '10'
'%e' % 12345.6                  # '1.234560e+04'

# string.Template
from string import Template
t = Template('Hello $name!')
t.substitute(name='Alice')     # 'Hello Alice!'
t.safe_substitute()             # no KeyError
string module Constants
import string
string.ascii_letters   # 'abcdef...ABCDEF...'
string.ascii_lowercase # 'abcdefghijklmnopqrstuvwxyz'
string.ascii_uppercase # 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
string.digits          # '0123456789'
string.hexdigits       # '0123456789abcdefABCDEF'
string.octdigits       # '01234567'
string.punctuation     # all punctuation chars
string.whitespace      # space, tab, newline...
string.printable       # all printable chars
πŸ”

Regular Expressions (re)

Metacharacters
PatternMeaning
.Any char except newline
^Start of string (or line w/ MULTILINE)
$End of string
*0 or more (greedy)
+1 or more
?0 or 1 (optional)
{m}Exactly m
{m,n}m to n repetitions
[]Character class [a-z] [^0-9]
|Alternation (OR)
()Capture group
\d / \DDigit / non-digit
\w / \WWord char / non-word
\s / \SWhitespace / non-ws
\b / \BWord boundary / non
\A / \ZAbsolute start / end
*? +? ??Non-greedy (lazy)
Functions & Flags
import re
re.match(r'\d+', '42abc')    # matches at start
re.search(r'\d+', 'abc42')   # anywhere in string
re.findall(r'\d+', 'a1b2')  # ['1','2']
re.finditer(r'\d+', 'a1b2') # iterator of matches
re.sub(r'\d+', '#', 'a1b2') # 'a#b#'
re.subn(r'\d+','#','a1b2')  # ('a#b#', 2)
re.split(r'[,;]', 'a,b;c')  # ['a','b','c']
re.fullmatch(r'\d+', '123') # match entire str
re.escape('a.b')            # 'a\.b'

# Compiled pattern (reuse)
p = re.compile(r'(\w+)=(\w+)')
m = p.search('key=value')
m.group(0)   # 'key=value' (full match)
m.group(1)   # 'key'
m.group(2)   # 'value'
m.groups()   # ('key', 'value')
m.start()    # 0
m.end()      # 9
m.span()     # (0, 9)

# Flags
re.IGNORECASE  # re.I β€” case-insensitive
re.MULTILINE   # re.M β€” ^ $ match lines
re.DOTALL      # re.S β€” . matches \n
re.VERBOSE     # re.X β€” allow comments
re.ASCII       # re.A β€” ASCII-only \w etc
Advanced Patterns
# --- Context Setup ---
import re
# ---------------------

# Named groups
p = re.compile(r'(?P<year>\d{4})-(?P<month>\d{2})')
m = p.search('2024-01')
m.groupdict()  # {'year':'2024','month':'01'}
m.group('year')  # '2024'

# Non-capturing group
r'(?:abc)+'    # group without capturing

# Lookahead / Lookbehind
r'\d+(?=px)'   # digits followed by 'px'
r'\d+(?!px)'   # digits NOT followed by 'px'
r'(?<=@)\w+'   # word after '@'
r'(?<!@)\w+'   # word NOT after '@'

# Backreference
r'(\w+)\s+\1'  # match repeated word

# Verbose (readable patterns)
p = re.compile(r"""
    (\d{4})   # year
    -(\d{2})  # month
    -(\d{2})  # day
""", re.VERBOSE)
πŸ“‹

Lists

Creation & Access
lst = []
lst = [1, 2, 3]
lst = list(range(5))       # [0,1,2,3,4]
lst = list('abc')          # ['a','b','c']
nested = [[1,2],[3,4]]

lst[0]       # first element
lst[-1]      # last element
lst[1:3]     # slice [1, 2]
lst[::2]     # every 2nd
lst[::-1]    # reversed copy
lst[1:4] = [10, 20]  # slice assignment
Methods
# --- Context Setup ---
lst = [1, 2, 3]
x = 0
# ---------------------

lst.append(4)         # add to end
lst.extend([5,6])     # add multiple
lst.insert(0, 99)     # insert at index
lst.remove(99)        # remove first match
lst.pop()             # remove & return last
lst.pop(0)            # remove & return idx 0
lst.clear()           # remove all elements
lst.copy()            # shallow copy
lst.index(3)          # first index of 3
lst.count(3)          # count occurrences
lst.sort()            # in-place sort
lst.sort(reverse=True) # descending
lst.sort(key=lambda x: x[1])
lst.reverse()         # in-place reverse

# Built-ins with lists
sorted(lst)           # new sorted list
sorted(lst, key=len)  # sort by length
reversed(lst)         # iterator (lazy)
min(lst) / max(lst)
sum(lst)
len(lst)
3 in lst              # membership test
Sorting with key functions
# --- Context Setup ---
lst = [1, 2, 3]
lst1 = [1, 2]
lst2 = [3, 4]
nested = []
objs = []
x = 0
# ---------------------

from operator import attrgetter, itemgetter

people = [{'name':'Bob','age':30},
          {'name':'Ann','age':25}]

# Sort by dict key
sorted(people, key=itemgetter('age'))
# Sort by multiple keys
sorted(people, key=itemgetter('age','name'))
# Sort objects by attribute
sorted(objs, key=attrgetter('name'))
# Reverse sort
sorted(lst, reverse=True)
# Stable sort (preserves equal order)
lst.sort(key=lambda x: x.lower())

# Copy strategies
import copy
shallow = lst.copy()        # or lst[:]
deep    = copy.deepcopy(lst)# fully independent

# Useful tricks
flat  = [x for sub in nested for x in sub]
unique = list(dict.fromkeys(lst))  # preserve order
lst1 + lst2    # concatenate
lst * 3        # repeat
Advanced Slicing & Comprehensions
# --- Context Setup ---
lst = [1, 2, 3]
matrix = [[1,2], [3,4]]
val = 0
x = 0
# ---------------------

# Advanced Slicing [start:stop:step]
lst[::-1]             # Reverse list
lst[-4:-1]           # Negative slicing
lst[::2] = [1, 2]    # Strided slice assignment

# List Comprehensions [expr for item in iter if cond]
[x**2 for x in lst]           # Basic
[x for x in lst if x%2==0]    # Filtered
[x if x>0 else 0 for x in lst] # Ternary (if/else)

# Nested List Comprehension (Matrix flattening)
[val for row in matrix for val in row]
Pitfalls & Edge Cases
# --- Context Setup ---
import copy
lst = [1, 2, 3]
# ---------------------

# 1. Assignment vs Shallow vs Deep Copy
a = [1, [2]]
b = a                  # Aliasing (same list)
c = a[:]               # Shallow copy (inner list shared)
d = copy.deepcopy(a)   # Deep copy (fully independent)

# 2. Mutating during iteration
for item in lst: pass      # BAD! Skips elements
for item in lst[:]: pass   # GOOD! Iterate over a copy

# 3. Default mutable arguments
def append_to(x, lst=[]): pass # BAD! lst is shared
πŸ”’

Tuples

Creation & Methods
t = ()                     # empty tuple
t = (1,)                   # single (comma required!)
t = (1, 2, 3)
t = 1, 2, 3               # packing (no parens)
t = tuple([1,2,3])
t = tuple('abc')          # ('a','b','c')
t = tuple(range(5))       # (0,1,2,3,4)

# Only 2 methods!
t.count(2)   # number of occurrences
t.index(2)   # index of first occurrence

# Cannot change elements
t[0] = 99    # TypeError!
Unpacking & Named Tuples
# Unpacking
x, y = (1, 2)
first, *rest = (1,2,3,4)    # rest is LIST
*init, last  = (1,2,3,4)
a, *mid, z   = (1,2,3,4,5)

# Swap without temp variable
a, b = b, a

# Tuple as dict key (hashable!)
d = {(0,0): 'origin', (1,2): 'point'}

# Named tuples
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
p.x   # 3
p[0]  # 3  (index still works)
p._asdict()  # {'x':3,'y':4}
p._replace(x=10)  # Point(x=10,y=4)

# Typed NamedTuple
from typing import NamedTuple
class Point(NamedTuple):
    x: int
    y: int = 0   # default value
Advanced Tuples
# --- Context Setup ---
x = 0
# ---------------------

# Slicing (same as lists, returns NEW tuple)
coords = (10, 20, 30, 40, 50)
coords[::-1]        # (50, 40, 30, 20, 10)
coords[1:4]        # (20, 30, 40)

# Tuple Comprehension (Generator)
# Using () creates a generator, wrap in tuple()
t = tuple(x**2 for x in range(5))  # (0, 1, 4, 9, 16)

# Membership Testing (O(N) for tuples)
30 in coords       # True
Pitfalls & Edge Cases
# 1. Single element tuple syntax
t1 = (42)    # Type: int!
t2 = (42,)   # Type: tuple (comma required)

# 2. The Paradox: Mutating inside an immutable tuple
t = (1, [2, 3])
t[1].append(99)  # ALLOWED (list mutated in place)
# t[1] = [2, 3]  # TypeError (cannot reassign index)

# 3. Shared nested objects with *
t = ([0],) * 3   # ([0], [0], [0]) - ALL point to same list!
πŸ”΅

Sets & Frozensets

Creation & Methods
s = {1, 2, 3}             # set literal
s = set([1, 1, 2, 3])     # from iterable
s = set()                 # empty (NOT {} ← that's dict)
fs = frozenset({1,2,3})  # immutable set

s.add(4)              # add element
s.remove(4)           # raises KeyError if missing
s.discard(4)          # no error if missing
s.pop()               # remove arbitrary element
s.clear()             # remove all
s.copy()              # shallow copy
Set Operations
a = {1,2,3};  b = {2,3,4}

# Operators
a | b   # {1,2,3,4}  union
a & b   # {2,3}      intersection
a - b   # {1}        difference
a ^ b   # {1,4}      symmetric difference
a <= b  # False      subset
a >= b  # False      superset
a < b   # proper subset

# Methods (same as above)
a.union(b)
a.intersection(b)
a.difference(b)
a.symmetric_difference(b)
a.issubset(b)
a.issuperset(b)
a.isdisjoint(b)       # True if no common

# In-place operations
a.update(b)                    # a |= b
a.intersection_update(b)       # a &= b
a.difference_update(b)         # a -= b
a.symmetric_difference_update(b) # a ^= b

# frozenset: hashable, usable as dict key
fs = frozenset({1,2,3})
d = {fs: 'value'}  # works!
Advanced Sets
# --- Context Setup ---
x = 0
# ---------------------

# Set Comprehensions (removes duplicates)
{x**2 for x in [1, -1, 2, -2]}  # {1, 4}
{x for x in range(10) if x % 2 == 0}

# Element Hashability Requirement
# Elements MUST be hashable (int, str, tuple)
# s = {1, [2, 3]}  # TypeError (list is unhashable)

# Fast Membership Testing (O(1) average)
'apple' in {'apple', 'banana'}  # True (Extremely fast)
Pitfalls & Edge Cases
# --- Context Setup ---
condition = True
s = "string"
# ---------------------

# 1. Empty set vs Empty dict
not_a_set = {}      # Creates an empty dict!
is_a_set = set()    # Creates an empty set

# 2. Mutating set during iteration
for x in s:
    if condition:
        s.remove(x) # RuntimeError: Set changed size during iteration

# 3. Sets of sets
# s = {{1, 2}, {3, 4}}  # TypeError: set is unhashable
s = {frozenset({1, 2}), frozenset({3, 4})} # Valid
πŸ“š

Dictionaries

Creation & Access
d = {'a':1, 'b':2}
d = dict(a=1, b=2)
d = dict([('a',1),('b',2)])
d = dict.fromkeys(['a','b'], 0)
d = {}  # empty dict (not set!)

# Access
d['a']                # 1 (KeyError if missing)
d.get('a')            # 1
d.get('z', 0)         # 0 (default)
d.setdefault('c', 3)  # insert if missing, return
Modify & Iterate
# --- Context Setup ---
cond = True
d = {"a": 1, "b": 2}
d1 = {"a": 1}
d2 = {"b": 2}
items = [1]
x = 0
# ---------------------

d['c'] = 3        # add/update
d.update({'c':30})  # merge
del d['c']         # delete
d.pop('c')         # remove & return
d.pop('c', None)   # safe pop
d.popitem()        # remove last inserted
d.clear()          # remove all

# Iteration
d.keys()           # dict_keys view
d.values()         # dict_values view
d.items()          # dict_items (k,v) view
for k, v in d.items(): ...

# Merge (Python 3.9+)
{**d1, **d2}       # merge (d2 wins)
d1 | d2            # merge operator
d1 |= d2           # in-place merge

# Dict comprehension
{k:v for k,v in items if cond}
{x: x**2 for x in range(5)}
collections module
# --- Context Setup ---
c2 = c2 = []
dict1 = {}
dict2 = {}
# ---------------------

from collections import (
    OrderedDict, defaultdict,
    Counter, ChainMap, deque
)

# defaultdict β€” no KeyError on missing key
dd = defaultdict(list)
dd['key'].append(1)   # auto-creates []
dd = defaultdict(int)   # for counters

# Counter β€” count frequencies
c = Counter('aabbbcc')       # {'b':3,'a':2,'c':2}
c = Counter([1,2,2,3])
c.most_common(2)            # top 2 items
c + c2 / c - c2              # arithmetic ops
c.elements()                 # iterator of elements
c.subtract('abc')           # subtract counts

# deque β€” double-ended queue
dq = deque([1,2,3], maxlen=5)
dq.appendleft(0)   # add to left
dq.popleft()       # remove from left
dq.rotate(2)       # rotate right
dq.extendleft([9]) # extend left

# ChainMap β€” lookup chain
cm = ChainMap(dict1, dict2)
cm['key']  # searches dict1 then dict2
Dict Comprehensions & Tricks
# --- Context Setup ---
d = {"a": 1, "b": 2}
k = "k"
v = 0
x = 0
# ---------------------

# Dict Comprehensions
{x: x**2 for x in range(5)}       # Basic
{k: v for k, v in d.items() if v>0}  # Filtered

# Inverting a Dictionary (swap keys/values)
inverted = {v: k for k, v in d.items()}

# Sorting a dictionary
dict(sorted(d.items()))                      # By keys
dict(sorted(d.items(), key=lambda x: x[1]))  # By values
Pitfalls & Edge Cases
# --- Context Setup ---
k = "k"
# ---------------------

# 1. dict.fromkeys() with mutable default
d = dict.fromkeys(['a', 'b'], [])
d['a'].append(1)  # BOTH 'a' and 'b' get [1]!
d2 = {k: [] for k in ['a', 'b']} # Correct

# 2. .keys(), .values(), .items() return VIEWS
keys = d.keys()
d['c'] = 3
print(keys)       # View dynamically updates to show 'c'

# 3. Unhashable keys
# d = {[1, 2]: 'val'} # TypeError!
d = {(1, 2): 'val'}   # Correct
⚑

Comprehensions & Generators

List, Dict & Set Comprehensions
# --- Context Setup ---
a = 0
d = {"a": 1, "b": 2}
import tempfile
f = tempfile.TemporaryFile(mode="w+")
inner = [1,2]
k = "k"
lst = [1, 2, 3]
matrix = [[1,2], [3,4]]
outer = [[1,2]]
v = 0
w = "w"
words = ["hello", "world"]
x = 0
# ---------------------

# List comprehension
[x**2 for x in range(10)]
[x for x in range(20) if x % 2 == 0]
[a if a>0 else -a for a in lst]  # if-else
[f(x) for x in outer for y in inner]  # nested
[x for row in matrix for x in row]   # flatten

# Walrus in comprehension (3.8+)
results = [y := f(x), y**2, y**3]

# Dict comprehension
{w: len(w) for w in ['hi', 'hello']}
{k:v for k,v in d.items() if v > 0}

# Set comprehension
{len(w) for w in words}
{x*2 for x in range(5) if x > 1}
Generator Expressions & Functions
# --- Context Setup ---
x = 0
# ---------------------

# Generator expression (lazy!)
gen = (x**2 for x in range(10))
sum(x**2 for x in range(10))  # no brackets needed
next(gen)  # 0 (first value)

# Generator function
def count_up(n):
    for i in range(n):
        yield i          # pause & return

def two_way():
    val = yield 'first'  # receive via send()
    yield f'got {val}'

g = count_up(3)
next(g)   # 0
next(g)   # 1
list(g)   # [2]

# yield from
def chain(*iters):
    for it in iters:
        yield from it

# send / throw / close
g = two_way()
next(g)         # 'first'
g.send('hi')   # 'got hi'
g.throw(ValueError)
g.close()       # stop generator
πŸ”„

itertools & functools

itertools β€” Infinite
import itertools as it

# Infinite iterators
it.count(10, 2)     # 10, 12, 14, ...
it.cycle('ABC')    # A, B, C, A, B, ...
it.repeat(5, 3)    # 5, 5, 5  (3 times)
itertools β€” Finite
# --- Context Setup ---
import itertools as it
x = 0
# ---------------------

it.accumulate([1,2,3,4])            # 1,3,6,10
it.chain([1,2],[3,4])               # 1,2,3,4
it.chain.from_iterable([[1],[2]])   # 1,2
it.compress('ABCD',[1,0,1,1])       # A,C,D
it.dropwhile(lambda x:x<3,[1,2,3,4])# 3,4
it.takewhile(lambda x:x<3,[1,2,3]) # 1,2
it.filterfalse(lambda x:x%2,range(5))# 0,2,4
it.islice(range(10), 2, 8, 2)       # 2,4,6
it.starmap(pow, [(2,3),(3,2)])       # 8,9
it.zip_longest([1,2],[3], fillvalue=0)
it.pairwise([1,2,3,4])              # (1,2),(2,3) 3.10+
it.groupby('AAABBBCC', key=lambda x:x)
itertools β€” Combinatoric
# --- Context Setup ---
import itertools as it
# ---------------------

it.product('AB', repeat=2)  # AA AB BA BB
it.permutations('ABC', 2)   # AB AC BA BC CA CB
it.combinations('ABC', 2)  # AB AC BC
it.combinations_with_replacement('ABC', 2)
                              # AA AB AC BB BC CC
functools
# --- Context Setup ---
a = 0
b = 0
func = lambda *args: None
# ---------------------

from functools import (
    reduce, partial, lru_cache,
    cache, wraps, total_ordering,
    cached_property, singledispatch
)

reduce(lambda a,b: a+b, [1,2,3,4])  # 10

double = partial(pow, exp=2)
double(base=5)   # 25

# Memoization
@lru_cache(maxsize=128)
def fib(n):
    return n if n<2 else fib(n-1)+fib(n-2)

@cache  # unlimited (3.9+)
def fib(n): ...

# Preserve metadata in decorators
@wraps(func)
def wrapper(*a, **kw): ...

# Type-based dispatch
@singledispatch
def process(arg): ...

@process.register(int)
def _(arg): ...  # for int
πŸ”€

Control Flow

Conditionals
# --- Context Setup ---
val = 0
x = 0
y = 0
# ---------------------

if x > 0:
    print('positive')
elif x < 0:
    print('negative')
else:
    print('zero')

# Ternary expression
result = 'yes' if x > 0 else 'no'

# Chained comparisons
1 < x < 10       # True if x in (1,10)
0 <= y <= 100

# Truthy / Falsy
# FALSY:  0, 0.0, '', [], {}, (), set(), None, False, b''
# TRUTHY: everything else

# Short-circuit evaluation
0 and 1/0    # 0   (1/0 never evaluated)
5 or 1/0     # 5   (1/0 never evaluated)
x = val or 'default'  # common pattern

any([0, '', 3])   # True (any truthy)
all([1, 'a', 3])  # True (all truthy)
match / case (Python 3.10+)
# --- Context Setup ---
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
command = None
p = {"age": 30}
point = (0, 0)
x = 0
y = 0
# ---------------------

match command:
    case 'quit': ...
    case 'start' | 'run': ...
    case {'action': a, 'item': i}: ...  # mapping
    case [x, y]: ...                       # sequence
    case Point(x=x, y=y): ...             # class
    case _ if x > 0: ...                  # guard
    case _: ...                            # wildcard

match point:
    case (0, 0): print('origin')
    case (x, 0): print(f'x-axis at {x}')
    case (0, y): print(f'y-axis at {y}')
    case (x, y) as p:
        print(f'point {p}')
Loops
# --- Context Setup ---
condition = True
d = {"a": 1, "b": 2}
done = False
lst = [1, 2, 3]
lst1 = [1, 2]
lst2 = [3, 4]
target = 0
# ---------------------

for i in range(5): ...          # 0,1,2,3,4
for i in range(2, 10, 2): ...   # 2,4,6,8
for i, v in enumerate(lst): ...  # with index
for i, v in enumerate(lst, start=1): ...
for a, b in zip(lst1, lst2): ... # parallel
for k, v in d.items(): ...       # dict
for ch in 'hello': ...           # string
for x in reversed(lst): ...      # reverse
for x in sorted(lst): ...        # sorted

# Loop control
break     # exit loop
continue  # skip to next iteration
pass      # no-op placeholder

# for...else (rarely known!)
for x in lst:
    if x == target: break
else:  # runs ONLY if loop completed w/o break
    print('not found')

# while loop
while condition: ...
while True:
    if done: break

# Unzip with zip
pairs = [(1,2),(3,4)]
a, b = zip(*pairs)  # unzip!
Loop Pitfalls & Patterns
# --- Context Setup ---
condition = True
lst = [1, 2, 3]
# ---------------------

# 1. Mutating list while iterating (BAD)
for item in lst:
    if condition: lst.remove(item)  # skips elements!

# FIX: iterate over a copy
for item in lst[:]: ...

# 2. Infinite while loop
count = 0
while count < 5:
    print(count)
    # count += 1  # Forgetting this -> infinite loop

# 3. range() off-by-one
list(range(5))    # [0, 1, 2, 3, 4] (5 is EXCLUDED)

# 4. Variable leakage
for i in range(3): pass
print(i)          # 2 (loop variables NOT block scoped)
πŸ”§

Functions

Definition & Arguments
def greet(name: str = 'World') -> str:
    '''Return greeting.'''
    return f'Hello, {name}!'

# Mutable default arg pitfall!
def append(item, lst=None):  # CORRECT
    if lst is None: lst = []
    lst.append(item)
    return lst

# Positional-only / keyword-only
def f(pos1, pos2, /, both, *, kw_only):
    pass
# pos1, pos2 MUST be positional (before /)
# kw_only MUST be keyword (after *)

# *args and **kwargs
def func(*args, **kwargs):
    print(args)    # tuple
    print(kwargs)  # dict

# Full order: pos β†’ *args β†’ kw-only β†’ **kwargs
def full(a, b, *args, c=10, **kw): ...

# Unpacking in calls
f(*[1,2,3])        # unpack list
f(**{'a':1,'b':2}) # unpack dict

# Multiple return values
def minmax(lst):
    return min(lst), max(lst)  # returns tuple
lo, hi = minmax([3,1,4])
Lambda & Higher-Order
# --- Context Setup ---
i = 0
n = 0
p = {"age": 30}
people = [{'name':'Bob','age':30}]
s = "string"
x = 0
y = 0
# ---------------------

# Lambda: lambda args: expression (No return!)
square = lambda x: x**2
add    = lambda x, y: x + y
grade  = lambda s: 'A' if s>=90 else 'B'

# Passing as arguments
sorted(people, key=lambda p: p['age'])

# Factory (Lambda returning lambda)
multiplier = lambda n: lambda x: x * n
double = multiplier(2)

# IIFE (Immediately invoked)
(lambda x: x*2)(5)  # 10

# Walrus in Lambda
f = lambda s: (n := len(s)) ** 2

# Loop capture pitfall! (Late Binding)
# BAD: fns = [lambda: i for i in range(3)]  # all 2!
fns = [lambda i=i: i for i in range(3)] # GOOD
Map, Filter & Reduce
# --- Context Setup ---
a = 0
b = 0
x = 0
# ---------------------

# map(func, iterable)
m = map(lambda x: x**2, [1, 2, 3])
print(list(m))  # [1, 4, 9]

# filter(func, iterable)
evens = filter(lambda x: x%2==0, [1,2,3,4])
print(list(evens)) # [2, 4]

# reduce(func, iterable)
from functools import reduce
prod = reduce(lambda a,b: a*b, [1,2,3,4])
print(prod)  # 24

# Note: map/filter return lazy iterators!
print(list(m))  # [] (Exhausted)
Scope (LEGB) & Returns
# LEGB Scope Rule:
# Local β†’ Enclosing β†’ Global β†’ Built-in
x = 'global'
def outer():
    x = 'enclosing'
    def inner():
        nonlocal x     # Modifies 'enclosing' x
        x = 'local'

# Modifying global scope
count = 0
def increment():
    global count   # Required to modify global
    count += 1

# Implicit return
def no_return(): pass
print(no_return()) # Output: None
Recursion & First-Class
# First-Class Function (assign to variable)
f = print
f('Hello')

# Recursion (function calls itself)
def factorial(n: int) -> int:
    if n <= 1: return 1  # Base case
    return n * factorial(n - 1)

# Recursion limit (default ~1000)
import sys
sys.setrecursionlimit(2000)
Pitfalls & Edge Cases
# 1. Calling function before definition
# Function name must be bound at call time.
# Fails if called during module top-level import.

# 2. Assigning print() result
result = print('hi') # result is None!
Decorators
# --- Context Setup ---
abstractmethod = None
dec1 = None
dec2 = None
name = "name"
# ---------------------

from functools import wraps

def my_decorator(func):
    @wraps(func)  # preserve __name__, __doc__
    def wrapper(*args, **kwargs):
        print('before')
        result = func(*args, **kwargs)
        print('after')
        return result
    return wrapper

@my_decorator
def say_hello(): print('hello')

# Decorator with arguments (factory)
def repeat(n):
    def decorator(func):
        @wraps(func)
        def wrapper(*a, **kw):
            for _ in range(n): func(*a, **kw)
        return wrapper
    return decorator

@repeat(3)
def hi(): print('hi')

# Stacking (applied bottom-up)
@dec1
@dec2   # dec1(dec2(f))
def f(): ...

# Built-in decorators
@staticmethod   # no self/cls
@classmethod    # cls as first arg
@property       # getter
@name.setter    # setter
@name.deleter   # deleter
@abstractmethod # from abc
def _dummy(): pass
Type Hints & Docstrings
# Docstrings (__doc__)
def area(r):
    """Return the area of a circle. (PEP 257)"""
    pass

from typing import Optional, Union, Any
from typing import Callable, TypeVar, Generic

# Basic type hints
def greet(name: str) -> str: ...
def f(x: int, y: Optional[str] = None) -> Union[int,str]: ...

# Modern syntax (3.10+)
def f(x: int | None = None) -> list[int]: ...

# Generic TypeVar
T = TypeVar('T')
def first(lst: list[T]) -> T:
    return lst[0]

# TypedDict
from typing import TypedDict
class Point(TypedDict):
    x: float
    y: float

# Callable[[arg_types], return_type]
def apply(fn: Callable[[int], str], x: int): ...

# Literal
from typing import Literal
def mode(m: Literal['r','w','a']): ...
🧠

Algorithmic Patterns

Factorial (Recursion)
def factorial(n: int) -> int:
    """Return n! using recursion. n >= 0."""
    if n <= 1:
        return 1
    return n * factorial(n - 1)
Binary Search (Iterative)
def binary_search(arr: list, target: int) -> int:
    """Return index of target in sorted arr, or -1."""
    low, high = 0, len(arr) - 1
    while low <= high:
        mid = (low + high) // 2
        if arr[mid] == target: return mid
        elif arr[mid] < target: low = mid + 1
        else: high = mid - 1
    return -1
Merge Two Dictionaries
def merge_dicts(d1: dict, d2: dict) -> dict:
    """Combine d1 & d2; d2 overrides d1."""
    return {**d1, **d2}  # Python 3.5+
    # Python 3.9+: d1 | d2
Remove Duplicates (Preserve Order)
def remove_duplicates(lst: list) -> list:
    """Return unique elements preserving order."""
    seen = set()
    result = []
    for item in lst:
        if item not in seen:
            seen.add(item)
            result.append(item)
    return result
Check Palindrome
# --- Context Setup ---
c = "c"
# ---------------------

def is_palindrome(s: str) -> bool:
    """True if string is palindrome (ignores symbols)."""
    cleaned = [c.lower() for c in s if c.isalnum()]
    return cleaned == cleaned[::-1]
Caesar Cipher
def caesar(text: str, shift: int) -> str:
    res = []
    for ch in text:
        if ch.isalpha():
            base = ord('A') if ch.isupper() else ord('a')
            offset = (ord(ch) - base + shift) % 26
            res.append(chr(base + offset))
        else: res.append(ch)
    return ''.join(res)
Word Frequency
def word_freq(sentence: str) -> dict:
    import re
    words = re.findall(r'\b\w+\b', sentence.lower())
    freq = {}
    for w in words:
        freq[w] = freq.get(w, 0) + 1
    return freq
Flatten Nested List
def flatten(nested: list) -> list:
    flat = []
    for item in nested:
        if isinstance(item, list):
            flat.extend(item)
        else: flat.append(item)
    return flat
Valid Parentheses
def is_valid(s: str) -> bool:
    stack = []
    mapping = {')':'(', ']':'[', '}':'{'}
    for ch in s:
        if ch in mapping.values():
            stack.append(ch)
        elif ch in mapping:
            if not stack or stack.pop() != mapping[ch]:
                return False
    return not stack
First Non-Repeating Char
def first_unique(s: str) -> str | None:
    from collections import Counter
    counts = Counter(s)
    for ch in s:
        if counts[ch] == 1:
            return ch
    return None
Abbreviate Name
def abbrev_name(name: str) -> str:
    """Robert Downey Jr. -> R.D.Jr."""
    parts = name.split()
    res = []
    for i, p in enumerate(parts):
        if i == len(parts)-1 and not p.endswith('.'):
            res.append(p)
        else: res.append(p[0].upper() + '.')
    return ''.join(res)
Anagram Check
# --- Context Setup ---
c = "c"
s = "string"
# ---------------------

def are_anagrams(s1: str, s2: str) -> bool:
    # Uses lambda for clean string normalisation
    clean = lambda s: sorted(c.lower() for c in s if c.isalnum())
    return clean(s1) == clean(s2)
πŸ—οΈ

Object-Oriented Programming

Classes
class Animal:
    species = 'Unknown'  # class attribute

    def __init__(self, name, age):
        self.name = name  # instance attribute
        self.age = age

    def __str__(self):     # for print()
        return f'Animal({self.name})'

    def __repr__(self):   # unambiguous repr
        return f'Animal({self.name!r},{self.age!r})'

    @classmethod
    def from_dict(cls, d):   # alternative constructor
        return cls(d['name'], d['age'])

    @staticmethod
    def is_vertebrate(): return True

    @property
    def info(self): return f'{self.name},{self.age}'

    @info.setter
    def info(self, val):
        self.name, self.age = val.split(',')

class Dog(Animal):   # single inheritance
    def __init__(self, name, age, breed):
        super().__init__(name, age)  # call parent
        self.breed = breed

isinstance(Dog(), Animal)  # True
issubclass(Dog, Animal)   # True
Dog.__mro__   # Method Resolution Order
Dunder / Magic Methods
CategoryMethods
Repr__str__ __repr__ __bytes__ __format__
Math__add__ __sub__ __mul__ __truediv__ __floordiv__ __mod__ __pow__
Math right__radd__ __rsub__ etc.
Math in-place__iadd__ __isub__ etc.
Comparison__eq__ __ne__ __lt__ __le__ __gt__ __ge__ __hash__
Container__len__ __getitem__ __setitem__ __delitem__ __contains__ __iter__ __next__
Context mgr__enter__ __exit__
Lifecycle__new__ __init__ __del__
Callable__call__(*args, **kwargs)
Attributes__getattr__ __setattr__ __delattr__ __getattribute__
Numeric__bool__ __int__ __float__ __abs__ __round__ __index__
Dataclasses (3.7+)
# --- Context Setup ---
p = {"age": 30}
# ---------------------

from dataclasses import dataclass, field
from dataclasses import asdict, astuple, replace

@dataclass
class Point:
    x: float
    y: float = 0.0
    tags: list = field(default_factory=list)

@dataclass(frozen=True)
class FrozenPoint: pass
class FrozenPoint: pass   # immutable
@dataclass(order=True)
class _Dummy1: pass    # comparison methods
@dataclass(slots=True)
class _Dummy2: pass    # __slots__ (3.10+)
@dataclass(kw_only=True)
class _Dummy3: pass  # keyword-only (3.10+)

asdict(p)     # {'x':1.0,'y':0.0,'tags':[]}
astuple(p)    # (1.0, 0.0, [])
replace(p, x=5.0)  # new instance changed x
Abstract Classes & Enums
# --- Context Setup ---
import math
# ---------------------

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

class Circle(Shape):
    def area(self) -> float:
        return math.pi * self.r ** 2

from enum import Enum, IntEnum, auto, unique

@unique
class Color(Enum):
    RED   = auto()   # 1
    GREEN = auto()   # 2
    BLUE  = auto()   # 3

Color.RED          # Color.RED
Color.RED.value    # 1
Color.RED.name     # 'RED'
list(Color)        # all members
Color['RED']      # by name
Color(1)           # by value
❗

Exceptions & Error Handling

Exception Hierarchy
  • BaseException
    • SystemExit β€” sys.exit()
    • KeyboardInterrupt β€” Ctrl+C
    • GeneratorExit
    • Exception
      • ArithmeticError: ZeroDivisionError, OverflowError
      • LookupError: IndexError, KeyError
      • ValueError, TypeError, AttributeError
      • NameError, UnboundLocalError
      • ImportError, ModuleNotFoundError
      • OSError: FileNotFoundError, PermissionError, TimeoutError
      • RuntimeError: RecursionError, NotImplementedError
      • StopIteration, MemoryError
      • UnicodeError: UnicodeDecodeError, UnicodeEncodeError
      • SyntaxError: IndentationError, TabError
try / except / else / finally
# --- Context Setup ---
e = Exception("e")
eg = Exception("eg")
x = 0
# ---------------------

try:
    result = 10 / x
except ZeroDivisionError:
    print('div by zero')
except (ValueError, TypeError) as e:
    print(f'Error: {e}')
except Exception as e:
    print(e.args, str(e))
    raise    # re-raise
else:
    print('success:', result)  # no exception
finally:
    print('always runs')       # cleanup

# Exception chaining
try:
    open('missing.txt')
except FileNotFoundError as e:
    raise RuntimeError('failed') from e
    raise RuntimeError('failed') from None  # suppress

# Exception groups (3.11+)
try:
    raise ExceptionGroup('eg', [ValueError(1), TypeError(2)])
except* ValueError as eg:
    print(eg.exceptions)
Custom Exceptions & Context Managers
# --- Context Setup ---
import tempfile
f = tempfile.TemporaryFile(mode="w+")
fnames = ["file.txt"]
import os
# ---------------------

# Custom exceptions
class ValidationError(ValueError):
    def __init__(self, field, msg):
        self.field = field
        super().__init__(f'{field}: {msg}')

raise ValidationError('email', 'invalid')

# Context manager class
class Timer:
    def __enter__(self):
        import time
        self.start = time.time()
        return self
    def __exit__(self, *args):
        self.elapsed = time.time()-self.start
        return False  # don't suppress exc

# @contextmanager
from contextlib import contextmanager

@contextmanager
def timer():
    import time
    start = time.time()
    yield       # 'with' block runs here
    print(time.time()-start)

from contextlib import suppress
with suppress(FileNotFoundError):
    os.remove('maybe_missing.txt')

# Multiple context managers
with open('a') as f1, open('b') as f2:
    ...

from contextlib import ExitStack
with ExitStack() as stack:
    files = [stack.enter_context(open(f)) for f in fnames]
πŸ“

File I/O & Paths

open() Modes & Usage
ModeDescription
'r'Read text (default)
'w'Write text (truncate)
'a'Append text
'x'Create (fail if exists)
'rb'/'wb'Binary read/write
'r+'Read + write
'w+'Read + write (truncate)
# Read entire file
with open('file.txt', encoding='utf-8') as f:
    content = f.read()

# Read line by line (memory efficient)
with open('file.txt') as f:
    for line in f:
        print(line.strip())
    lines = f.readlines()  # list of all lines
    line  = f.readline()   # one line

# Write
with open('out.txt', 'w', encoding='utf-8') as f:
    f.write('Hello\n')
    f.writelines(['a\n', 'b\n'])

# Seek / Tell
f.seek(0)   # go to start
f.tell()    # current position (bytes)
f.flush()   # flush buffer
pathlib (Recommended)
# --- Context Setup ---
new = 0
# ---------------------

from pathlib import Path

p = Path('/home/user/docs/file.txt')
p.name       # 'file.txt'
p.stem       # 'file'
p.suffix     # '.txt'
p.suffixes   # ['.txt']
p.parent     # Path('/home/user/docs')
p.parts      # ('/', 'home', 'user', ...)
p.anchor     # '/'

Path.cwd()   # current directory
Path.home()  # home directory
p / 'subdir' / 'file.py'  # join paths

p.exists();  p.is_file();  p.is_dir()
p.is_symlink(); p.is_absolute()
p.stat()     # os.stat_result

p.mkdir(parents=True, exist_ok=True)
p.rmdir()    # must be empty
p.touch()    # create file
p.unlink()   # delete file
p.rename(new); p.replace(new)  # move

p.read_text(encoding='utf-8')
p.write_text('content')
p.read_bytes(); p.write_bytes(b'data')

list(p.iterdir())      # dir contents
list(p.glob('*.py'))  # pattern match
list(p.rglob('*.py')) # recursive
p.resolve()            # absolute real path
p.relative_to('/home/user')  # 'docs/file.txt'
os & shutil
import os, shutil

os.getcwd()                    # current dir
os.chdir('/tmp')              # change dir
os.listdir('.')               # list entries
os.makedirs('a/b', exist_ok=True)
os.remove('file.txt')
os.rename('old', 'new')
os.stat('file.txt')

os.environ.get('HOME')
os.getenv('PATH', '')

os.path.join('a','b','c')    # 'a/b/c'
os.path.exists('file')
os.path.dirname('/a/b/c')    # '/a/b'
os.path.basename('/a/b/c')   # 'c'
os.path.splitext('f.txt')    # ('f', '.txt')
os.path.abspath('.')
os.path.expanduser('~')

# Walk directory tree
for root, dirs, files in os.walk('.'):
    for f in files:
        print(os.path.join(root, f))

shutil.copy('src', 'dst')     # copy file
shutil.copy2('src', 'dst')    # copy + metadata
shutil.copytree('src', 'dst') # copy directory
shutil.rmtree('dir')          # remove directory
shutil.move('src', 'dst')
shutil.make_archive('out', 'zip', 'dir')
πŸ“¦

Modules, Packages & Standard Library

Imports
import math                    # module
from math import sqrt, pi       # specific
from math import *             # all (avoid!)
import numpy as np             # alias
from os.path import join as pjoin

# Relative imports (inside packages)
from . import sibling_module
from .. import parent_module
from .utils import helper

# Conditional import
try:
    import ujson as json
except ImportError:
    import json

# __all__ controls 'from mod import *'
__all__ = ['PublicClass', 'public_func']

# sys.path manipulation
import sys
sys.path.append('/my/custom/path')
sys.path.insert(0, '.')  # highest priority

import importlib
mod = importlib.import_module('os.path')
importlib.reload(mod)
sys module
import sys
sys.argv          # ['script.py','arg1'...]
sys.path          # module search paths
sys.modules       # loaded modules cache
sys.version       # '3.12.0 ...'
sys.platform      # 'linux'/'win32'/'darwin'
sys.exit(0)       # exit with code 0
sys.stdin / sys.stdout / sys.stderr
sys.getrecursionlimit()  # 1000
sys.setrecursionlimit(5000)
sys.getsizeof([1,2,3])   # bytes
datetime
from datetime import date, time, datetime, timedelta

today = date.today()           # date(2024,1,15)
now   = datetime.now()         # current datetime
d     = date(2024, 1, 15)
dt    = datetime(2024, 1, 15, 10, 30, 0)

d.year / d.month / d.day
dt.hour / dt.minute / dt.second

td = timedelta(days=7, hours=3)
future = now + td
diff   = date(2024,12,31) - today
diff.days    # int days

now.strftime('%Y-%m-%d %H:%M:%S')  # format
datetime.strptime('2024-01-15', '%Y-%m-%d')

from datetime import timezone
datetime.now(timezone.utc)  # UTC aware
json & csv
# --- Context Setup ---
obj = {"key": "value"}
# ---------------------

import json

json.dumps({'a':1, 'b':[1,2,3]})
json.dumps(obj, indent=2, sort_keys=True)
json.dumps(obj, default=str)  # non-serializable

with open('data.json', 'w') as f:
    json.dump(obj, f, indent=2)

json.loads('{"a": 1}')
with open('data.json') as f:
    data = json.load(f)

import csv
with open('data.csv') as f:
    for row in csv.reader(f): ...
    for row in csv.DictReader(f):
        print(row['name'])

with open('out.csv', 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(['name', 'age'])
    writer.writerows([['Alice', 30], ['Bob', 25]])
logging
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s %(levelname)s %(message)s',
    filename='app.log'
)

logging.debug('debug')    # level 10
logging.info('info')     # level 20
logging.warning('warn')  # level 30
logging.error('error')   # level 40
logging.critical('crit') # level 50

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(
    logging.Formatter('%(name)s: %(message)s')
)
logger.addHandler(handler)
⚑

Async Programming & Concurrency

asyncio
# --- Context Setup ---
async_generator = async def async_generator(): yield 1
fetch = async def fetch(url): pass
long_operation = async def long_operation(): pass
# ---------------------

# --- Context Setup ---
import sys
class aiofiles:
    @staticmethod
    def open(*args, **kwargs):
        class AsyncFile:
            async def __aenter__(self): return self
            async def __aexit__(self, *args): pass
            async def read(self): return ""
            async def write(self, data): pass
        return AsyncFile()
async def async_generator(): yield 1
async def fetch(url): pass
async def long_operation(): pass
url = "http://example.com"
# ---------------------

import asyncio

# Coroutine function
async def fetch(url):
    await asyncio.sleep(1)  # non-blocking
    return f'fetched {url}'

# Run coroutine
asyncio.run(fetch('http://example.com'))

# Run multiple concurrently
async def main():
    results = await asyncio.gather(
        fetch('url1'),
        fetch('url2'),
        fetch('url3'),
    )
    return results

# Tasks
async def main():
    task = asyncio.create_task(fetch('url'))
    await asyncio.sleep(0)   # yield control
    result = await task

# Async for / async with
async for item in async_generator(): ...
async with aiofiles.open('file') as f:
    data = await f.read()

# asyncio primitives
lock = asyncio.Lock()
async with lock: ...
event = asyncio.Event()
await event.wait(); event.set()
sem = asyncio.Semaphore(5)
q = asyncio.Queue()
await q.put(item); item = await q.get()

# Timeout (3.11+)
async with asyncio.timeout(5.0):
    await long_operation()
threading & multiprocessing
# --- Context Setup ---
callback = lambda: None
# ---------------------

import threading

def worker(n): print(f'Thread {n}')
t = threading.Thread(target=worker, args=(1,))
t.daemon = True  # dies with main thread
t.start()
t.join()         # wait for completion

lock  = threading.Lock()
rlock = threading.RLock()   # reentrant
with lock:
    shared_counter += 1

event = threading.Event()
event.wait(); event.set(); event.clear()
sem   = threading.Semaphore(5)
timer = threading.Timer(5.0, callback)
timer.start(); timer.cancel()

# multiprocessing
from multiprocessing import Process, Pool, Queue

def worker(n): ...
p = Process(target=worker, args=(1,))
p.start(); p.join()

# Pool for parallel map
with Pool(4) as pool:
    results = pool.map(worker, range(10))
    # also: pool.imap(), pool.starmap()

q = Queue()
q.put('data'); q.get(); q.empty()
🌐

Virtual Environments & Packaging

venv & pip
# Create virtual environment
$ python -m venv .venv
$ python3 -m venv .venv

# Activate
$ .venv\Scripts\activate        # Windows (cmd)
$ .venv\Scripts\Activate.ps1   # Windows (PS)
$ source .venv/bin/activate    # macOS/Linux
$ deactivate                   # deactivate

# pip commands
$ pip install requests
$ pip install 'requests>=2.28'
$ pip install requests==2.28.2
$ pip install -r requirements.txt
$ pip uninstall requests
$ pip list
$ pip show requests
$ pip freeze > requirements.txt
$ pip install --upgrade pip
$ pip install --user package   # user-level

# pipx (isolated tool install)
$ pipx install black
$ pipx run black script.py
Code Quality & pyproject.toml
# Code quality tools
$ black .         # auto-format
$ isort .         # sort imports
$ flake8 .        # style / lint
$ pylint mymod.py # deep linting
$ mypy mymod.py   # type checking
$ pytest          # run tests
$ pytest -v       # verbose
$ pytest -k 'test_name'  # filter
$ pytest --cov=. --cov-report=html

# pyproject.toml (modern)
[build-system]
requires = ['setuptools>=61']

[project]
name = 'mypackage'
version = '1.0.0'
dependencies = ['requests>=2.28']

[project.optional-dependencies]
dev = ['pytest', 'black', 'mypy']
πŸ”¬

Advanced Topics

Custom Iterators
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 x in Countdown(3): print(x)  # 3 2 1

it = iter([1,2,3])
next(it)        # 1
next(it, 0)     # 0 as default (no StopIteration)
Descriptors
class Descriptor:
    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):
        obj.__dict__[self.name] = value

    def __delete__(self, obj):
        del obj.__dict__[self.name]

class MyClass:
    attr = Descriptor()  # class-level!
Metaclasses
# --- Context Setup ---
class Base: pass
# ---------------------

# type() creates classes dynamically
MyClass = type('MyClass', (Base,), {'attr': 1})

class Meta(type):
    def __new__(mcs, name, bases, namespace):
        print(f'Creating class {name}')
        return super().__new__(mcs, name, bases, namespace)

class MyClass(metaclass=Meta):
    pass

# __init_subclass__
class Base:
    def __init_subclass__(cls, **kw):
        super().__init_subclass__(**kw)
        print(f'Subclass {cls} created')
__slots__ & Performance
# --- Context Setup ---
stmt = "pass"
# ---------------------

class Point:
    __slots__ = ['x', 'y']  # no __dict__!
    def __init__(self, x, y):
        self.x = x; self.y = y
# ~40% less memory per instance
# Cannot add arbitrary attributes

# timeit
import timeit
timeit.timeit('x=2**10', number=100000)
timeit.repeat(stmt, number=1000, repeat=5)

# cProfile
import cProfile
cProfile.run('my_function()')

# Debugger
import pdb; pdb.set_trace()  # breakpoint
breakpoint()  # Python 3.7+ shorthand
# pdb commands: n s c l p pp b q u d r a

# tracemalloc
import tracemalloc
tracemalloc.start()
snapshot = tracemalloc.take_snapshot()
stats = snapshot.statistics('lineno')
✨

Pythonic Patterns & Best Practices

Pythonic Idioms
# --- Context Setup ---
d = {"a": 1, "b": 2}
default = 0
key = ""
lst = [1, 2, 3]
lst1 = [1, 2]
lst2 = [3, 4]
parts = []
word = ""
word_count = {}
x = 0
# ---------------------

# Swap variables
a, b = b, a

# Check None (correct)
if x is None: ...
if x is not None: ...

# Truthiness (not len check)
if lst: ...     # not: if len(lst) > 0
if not lst: ... # not: if len(lst) == 0

# EAFP (Python-preferred)
try:
    val = d[key]
except KeyError:
    val = default

# String join (O(n), not O(nΒ²))
result = ''.join(parts)  # fast
# NOT: for p in parts: result += p

# dict.get() for defaults
count = word_count.get(word, 0) + 1

# enumerate instead of range+index
for i, v in enumerate(lst): ...
# NOT: for i in range(len(lst)): lst[i]

# zip for parallel iteration
for a, b in zip(lst1, lst2): ...

# Context managers for resources
with open('f') as f: ...  # not: open() then close()
Common Pitfalls
# --- Context Setup ---
i = 0
# ---------------------

# 1. Mutable default argument
def f(lst=[]): ...      # BUG: shared!
def f(lst=None):        # CORRECT
    if lst is None: lst = []

# 2. Float comparison
0.1 + 0.2 == 0.3              # False!
abs(0.1+0.2-0.3) < 1e-9      # True

# 3. Late binding in closures
fns = [lambda: i for i in range(3)] # all 2!
fns = [lambda i=i: i for i in range(3)] # 0,1,2

# 4. Chained assignment with mutable
a = b = []     # SAME list!
a.append(1); print(b)  # [1] β€” shared!

# 5. Integer caching
a = 1000; b = 1000
a is b    # False (not guaranteed for >256)
a == b    # True  (use == for values!)

# 6. += on immutables
t = (1,2); old_id = id(t)
t += (3,); id(t) == old_id  # False! new obj
PEP 8 Quick Reference
  • Indentation: 4 spaces (no tabs)
  • Max line: 79 chars (88 with black)
  • Blank lines: 2 between top-level defs, 1 between methods
  • Imports order: stdlib β†’ third-party β†’ local
  • Keyword args: no spaces β†’ f(x=1)
  • Operators: spaces β†’ a = b + c
  • No trailing whitespace; files end with newline
Naming Conventions
StyleUse For
snake_casevariables, functions, modules
PascalCaseclasses
UPPER_SNAKEconstants
_privateinternal use convention
__mangledname mangling in class
__dunder__magic/special methods
subprocess & argparse
import subprocess
r = subprocess.run(
    ['ls', '-la'],
    capture_output=True, text=True
)
r.stdout; r.returncode; r.stderr
subprocess.check_output(['cmd'], text=True)

import argparse
p = argparse.ArgumentParser(description='Tool')
p.add_argument('filename')              # positional
p.add_argument('-v', '--verbose', action='store_true')
p.add_argument('-n', type=int, default=5)
p.add_argument('--choices', choices=['a','b'])
args = p.parse_args()
args.filename; args.verbose; args.n
πŸ“š

Standard Library Highlights

heapq
# --- Context Setup ---
val = 0
# ---------------------

import heapq
lst = [3,1,4,1,5,9,2,6]
heapq.heapify(lst)           # in-place min-heap
heapq.heappush(lst, 0)      # push
heapq.heappop(lst)           # pop smallest
heapq.heappushpop(lst, 7)   # push then pop
heapq.heapreplace(lst, 7)   # pop then push
heapq.nlargest(3, lst)      # [9,6,5]
heapq.nsmallest(3, lst)     # [0,1,1]
# Max-heap: negate values
heapq.heappush(lst, -val)   # simulate max-heap
io β€” StringIO & BytesIO
# --- Context Setup ---
import csv
# ---------------------

from io import StringIO, BytesIO

sio = StringIO()
sio.write('Hello\nWorld')
sio.getvalue()  # 'Hello\nWorld'
sio.seek(0)
sio.read()      # 'Hello\nWorld'
# Use as file-like object
csv.reader(StringIO('a,b,c'))

bio = BytesIO(b'binary data')
bio.read(6)    # b'binary'
hashlib & base64
import hashlib
hashlib.md5(b'hello').hexdigest()
hashlib.sha256(b'hello').hexdigest()
hashlib.sha512(b'hello').hexdigest()
h = hashlib.new('sha256')
h.update(b'hello'); h.update(b' world')
h.hexdigest()

import base64
base64.b64encode(b'hello world')
base64.b64decode(b'aGVsbG8gd29ybGQ=')
base64.urlsafe_b64encode(b'data?')
unittest & mock
import unittest
from unittest.mock import patch, MagicMock

class TestMyCode(unittest.TestCase):
    def setUp(self):
        self.data = [1,2,3]

    def test_sum(self):
        self.assertEqual(sum(self.data), 6)
        self.assertTrue(len(self.data) > 0)
        self.assertIn(2, self.data)
        self.assertRaises(ValueError, int, 'abc')

    @unittest.skip('reason')
    def test_skipped(self): ...

    @patch('module.function')
    def test_mock(self, mock_fn):
        mock_fn.return_value = 42

if __name__ == '__main__':
    unittest.main()
copy Β· pprint Β· textwrap Β· tempfile
# --- Context Setup ---
obj = {"key": "value"}
# ---------------------

import copy
copy.copy(obj)      # shallow copy
copy.deepcopy(obj)  # deep copy

import pprint
pprint.pprint({'a': [1,2,3], 'b': {'c':4}})
pp = pprint.PrettyPrinter(indent=2, width=40)

import textwrap
textwrap.wrap('long text here', width=20)
textwrap.fill('long text here', width=20)
textwrap.dedent('''
    indented text
    second line''')  # remove common indent

import tempfile
tempfile.NamedTemporaryFile()
tempfile.TemporaryDirectory()
tempfile.mkstemp(); tempfile.mkdtemp()
All 68 Built-in Functions
absaiterallanyanextasciibinboolbreakpointbytearraybytescallablechrclassmethodcompilecomplexdelattrdictdirdivmodenumerateevalexecfilterfloatformatfrozensetgetattrglobalshasattrhashhelphexidinputintisinstanceissubclassiterlenlistlocalsmapmaxmemoryviewminnextobjectoctopenordpowprintpropertyrangereprreversedroundsetsetattrslicesortedstaticmethodstrsumsupertupletypevarszip__import__