Cheat sheet · Python
Python Cheat Sheet
A scannable Python 3 reference covering variables, f-strings, string methods, lists, dicts, sets, tuples, comprehensions, control flow, functions, built-ins, exceptions, and file handling.
A cheat sheet is for the thing you have understood once and cannot quite remember the shape of. It is written to be scanned, so the common cases come first. Starting from nothing? The Python exercises are the right first step; come back here once the syntax is something you are recalling rather than meeting. Looking for another language? See every cheat sheet.
This page is a fast-scanning reference for Python 3 syntax you'll reach for constantly — not a
tutorial. Each section is a self-contained group of runnable snippets, so jump straight to the part
you need. Where a line's output isn't obvious from reading it, the comment after it (or a text
block underneath) shows exactly what Python produces.
Variables & Types
Python variables don't need a declared type — the type lives with the value, not the variable name, and it can change if you reassign the variable to something else.
name = "Priya" # str
age = 27 # int
gpa = 3.85 # float
is_active = True # bool
data = None # NoneType — represents "no value"
type(age) # <class 'int'>
isinstance(age, int) # TrueConverting between types is done with int(), float(), str(), and bool(). Note that int()
on a float truncates toward zero — it does not round:
int("42") # 42
int(3.9) # 3 — truncated, not rounded
float("2.5") # 2.5
str(17) # '17'
bool(0) # False
bool("") # False
bool("False") # True — any non-empty string is truthyPython lets you assign several variables at once, which is handy for swapping values or unpacking a known-length sequence:
x, y = 1, 2
x, y = y, x # swap without a temp variable — x=2, y=1
a = b = c = 0 # all three names point at 0
first, *rest = [1, 2, 3, 4] # first=1, rest=[2, 3, 4]String Formatting with f-strings
An f-string is a string literal prefixed with f. Anything inside {} is evaluated as a real
Python expression and inserted into the string — this is the standard way to build strings in
modern Python.
name = "Priya"
score = 87.4567
f"Hello, {name}!" # 'Hello, Priya!'
f"Score: {score:.1f}" # 'Score: 87.5' — 1 decimal place
f"Score: {score:.2f}%" # 'Score: 87.46%' — 2 decimal places
f"{name!r}" # "'Priya'" — !r inserts repr() instead of str()
f"{1_000_000:,}" # '1,000,000' — thousands separator
f"{7:05d}" # '00007' — zero-padded to width 5
f"{score:>10.2f}" # ' 87.46' — right-aligned in a width-10 field
f"{score:<10.2f}|" # '87.46 |' — left-aligned, padding visible before |
f"{2 ** 10 = }" # '2 ** 10 = 1024' — self-documenting expressionThe = form in the last line (added in Python 3.8) is a debugging shortcut: it prints the literal
expression text alongside its value, so you don't have to type the variable name twice. You can also
call functions and use conditionals directly inside the braces:
items = ["pen", "book"]
f"You have {len(items)} item{'s' if len(items) != 1 else ''}" # 'You have 2 items'Common String Methods
Strings are immutable — every method below returns a new string rather than changing the original in place.
s = " Hello, World! "
s.strip() # 'Hello, World!' — removes leading/trailing whitespace
s.lower() # ' hello, world! '
s.upper() # ' HELLO, WORLD! '
s.strip().replace("World", "Python") # 'Hello, Python!'
s.strip().split(",") # ['Hello', ' World!']
"-".join(["a", "b", "c"]) # 'a-b-c' — opposite of split()
"Hello".startswith("He") # True
"Hello".endswith("lo") # True
"hello world".title() # 'Hello World'
"42".isdigit() # True
"".isdigit() # False — empty string has no digitsfind() and index() both search for a substring, but they disagree about what to do when nothing
is found: find() returns -1, while index() raises a ValueError. Prefer find() when a
missing match is a normal outcome, and index() when it should be treated as a bug:
"Hello".find("l") # 2 — index of the first match
"Hello".find("z") # -1 — not found, no exception
"Hello".count("l") # 2 — how many times "l" appearsLists
A list is an ordered, mutable collection — you can change, add to, or remove from it after creation, and it can hold a mix of types.
fruits = ["apple", "banana", "cherry"]
fruits.append("date") # add to the end
fruits.insert(1, "kiwi") # insert at a specific index, shifting the rest right
fruits.remove("banana") # delete the first item that equals "banana"
fruits.pop() # remove and return the last item
fruits.pop(0) # remove and return the item at index 0
fruits.extend(["fig", "grape"]) # append every item from another iterable
fruits.sort() # sort in place, ascending
fruits.sort(reverse=True) # sort in place, descending
fruits.reverse() # reverse order in place
fruits.count("kiwi") # how many times "kiwi" appears
fruits.index("fig") # index of the first "fig"
"kiwi" in fruits # membership test — True/False
len(fruits) # number of itemsappend() adds one item (even if that item is itself a list); extend() adds every item from an
iterable individually. Mixing them up is a common bug:
a = [1, 2]
a.append([3, 4]) # [1, 2, [3, 4]] — one new item, a nested list
b = [1, 2]
b.extend([3, 4]) # [1, 2, 3, 4] — two new itemsSlicing
list[start:stop:step] pulls out a sub-list. start is included, stop is excluded, and step
defaults to 1. Any of the three can be left out.
nums = [10, 20, 30, 40, 50]
nums[1:3] # [20, 30]
nums[:2] # [10, 20] — from the beginning
nums[-2:] # [40, 50] — negative indices count from the end
nums[::-1] # [50, 40, 30, 20, 10] — reversed copy
nums[::2] # [10, 30, 50] — every other itemA slice always builds a brand-new list, so nums[:] is a quick way to copy a list without importing
anything.
List Comprehensions
A comprehension builds a new list from an existing iterable in a single readable line — it's
shorthand for a for loop that appends to an empty list.
squares = [n ** 2 for n in range(6)] # [0, 1, 4, 9, 16, 25]
evens = [n for n in range(10) if n % 2 == 0] # [0, 2, 4, 6, 8]
labels = ["even" if n % 2 == 0 else "odd" for n in range(4)] # ['even', 'odd', 'even', 'odd']
flat = [x for row in [[1, 2], [3, 4]] for x in row] # [1, 2, 3, 4] — flattens one levelThe if at the end of a comprehension filters which items are included; an if/else placed
right after the expression instead chooses a value for every item, the way you saw in labels
above — the two look similar but do very different jobs.
Dictionaries
A dictionary maps keys to values. Since Python 3.7, a dict remembers the order keys were inserted in.
person = {"name": "Zoe", "age": 30}
person["age"] # 30 — raises KeyError if "age" were missing
person.get("age") # 30 — same lookup, but safe
person.get("email") # None — missing key, no error, no fallback given
person.get("email", "n/a") # 'n/a' — missing key, with a fallback
person["email"] = "zoe@example.com" # adds a new key (or overwrites an existing one)
person.pop("age") # 30 — removes "age" and returns its value
"name" in person # True — checks the keys, not the valuesSquare-bracket lookup (person["age"]) raises a KeyError for a missing key; .get() never
raises — it hands back None, or whatever fallback you pass as the second argument. Reach for
.get() whenever a missing key is a normal, expected possibility.
config = {"debug": False}
config.update({"debug": True, "retries": 3}) # merges keys in place, overwriting duplicates
print(config){'debug': True, 'retries': 3}To combine two dicts into a brand-new one without mutating either, use ** unpacking or the |
merge operator (Python 3.9+) — later dicts win on duplicate keys:
defaults = {"theme": "dark", "retries": 3}
overrides = {"retries": 5, "verbose": True}
{**defaults, **overrides} # {'theme': 'dark', 'retries': 5, 'verbose': True}
defaults | overrides # same result, more readablesetdefault(key, default) is a one-line way to fetch a key, inserting default first if it's
missing — useful for building up counts or groups without checking in first:
counts = {}
counts.setdefault("a", 0)
counts["a"] += 1
print(counts){'a': 1}Dictionary Comprehensions
nums = [1, 2, 3, 4]
{n: n ** 2 for n in nums} # {1: 1, 2: 4, 3: 9, 4: 16}
{n: n ** 2 for n in nums if n % 2 == 0} # {2: 4, 4: 16}
{v: k for k, v in {"a": 1, "b": 2}.items()} # {1: 'a', 2: 'b'} — swap keys and valuesSets
A set is an unordered collection of unique, hashable values — duplicates are silently dropped, and sets are built for fast membership tests and the classic set-algebra operations.
a = {1, 2, 3}
b = {3, 4, 5}
a | b # {1, 2, 3, 4, 5} — union: everything in either set
a & b # {3} — intersection: only what's in both
a - b # {1, 2} — difference: in a, but not in b
a ^ b # {1, 2, 4, 5} — symmetric difference: in exactly one set
a.add(10) # adds a single value
a.discard(100) # removes a value if present — no error if it's missing
3 in a # Trueset(some_list) is a common idiom for deduplicating a list — the result loses the original order,
so sort it afterward if order matters:
sorted(set([3, 1, 2, 3, 1])) # [1, 2, 3]Sets also support comprehensions, using the same {} syntax as dict comprehensions but with a
single expression instead of a key: value pair:
{c.upper() for c in "banana"} # {'B', 'A', 'N'} — unique letters onlyTuples
A tuple is an ordered collection like a list, but immutable — once created, you can't add, remove, or reassign its items.
point = (3, 4)
x, y = point # unpacking — x=3, y=4
point[0] # 3
len(point) # 2
point.count(3) # 1 — occurrences of the value 3A single-item tuple needs a trailing comma, because parentheses alone don't make something a tuple:
single = (5,) # a one-item tuple
not_a_tuple = (5) # just the int 5, in parenthesesTrying to change a tuple in place raises TypeError, which is exactly the point — it's a signal
that this data shouldn't change:
point = (3, 4)
point[0] = 10TypeError: 'tuple' object does not support item assignmentBecause tuples are immutable (and therefore hashable, as long as every item inside them is too), they can be used as dictionary keys or stored in a set — lists never can:
distances = {(0, 0): 0, (1, 1): 1.41}
distances[(0, 0)] # 0Reach for a list when the contents need to change over time, and a tuple for a fixed, related group of values — like coordinates, or a database row.
Control Flow
if / elif / else
Python has no switch statement — a chain of if/elif/else covers the same ground, and only
the first matching branch runs.
age = 20
if age < 13:
stage = "child"
elif age < 20:
stage = "teen"
else:
stage = "adult"
print(stage)adultFor a simple choice between two values, a conditional expression (Python's version of a ternary operator) fits on one line:
n = 7
label = "even" if n % 2 == 0 else "odd" # 'odd'for Loops
A for loop walks through any iterable — a list, string, dict, range(), and so on — handing you
each item directly.
for fruit in ["apple", "banana", "cherry"]:
print(fruit)apple
banana
cherryrange(start, stop, step) generates numbers without building a list in memory, which is why it's
the default choice for "do this N times" or "loop by index":
list(range(5)) # [0, 1, 2, 3, 4] — stop only: 0 up to (not including) 5
list(range(2, 10, 2)) # [2, 4, 6, 8] — start, stop, step
list(range(5, 0, -1)) # [5, 4, 3, 2, 1] — counting downwhile Loops
A while loop keeps running as long as its condition stays true — use it when you don't know the
number of iterations in advance.
total = 0
n = 1
while n <= 5:
total += n
n += 1
print(total)15break, continue, and for...else
break exits a loop immediately; continue skips straight to the next iteration without finishing
the current one.
for n in range(10):
if n == 3:
continue
if n == 6:
break
print(n)0
1
2
4
5A for loop can also carry an else clause, which runs only if the loop finished without
hitting a break — a clean way to express "search and report if nothing was found":
def has_factor(n, candidates):
for c in candidates:
if n % c == 0:
print(f"{n} is divisible by {c}")
break
else:
print(f"{n} has no factor in {candidates}")
has_factor(17, [2, 3, 5])
has_factor(15, [2, 3, 5])17 has no factor in [2, 3, 5]
15 is divisible by 3Functions
def defines a function; parameters after = are optional and use the given default when the
caller omits them.
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Sam"))
print(greet("Sam", "Hey"))Hello, Sam!
Hey, Sam!Watch out for mutable default arguments. A default value is created exactly once, when the function is defined — not on every call. If that default is a mutable object like a list, every call that relies on the default shares the same object:
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item("apple"))
print(add_item("banana"))['apple']
['apple', 'banana']The second call's basket already contains "apple", because both calls reused the one list created
when add_item was defined. The standard fix is to default to None and create a fresh list inside
the function body:
def add_item_safe(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket*args and **kwargs
*args collects any extra positional arguments into a tuple; **kwargs collects any extra keyword
arguments into a dict. Use them when a function needs to accept a variable number of inputs.
def total(*numbers, **labels):
print("numbers:", numbers)
print("labels:", labels)
return sum(numbers)
result = total(1, 2, 3, unit="kg", source="scale")
print(result)numbers: (1, 2, 3)
labels: {'unit': 'kg', 'source': 'scale'}
6A bare * in a parameter list (with no name after it) doesn't collect anything itself — it just
forces every parameter after it to be passed by keyword only, which is a good habit for
arguments where the order isn't obvious:
def connect(host, *, port=443, secure=True):
return f"{host}:{port} (secure={secure})"
print(connect("example.com", port=8080))example.com:8080 (secure=True)Multiple Return Values
Returning several values separated by commas actually returns one tuple, which you can unpack right back into separate names at the call site.
def min_max(values):
return min(values), max(values)
lowest, highest = min_max([4, 1, 7, 3])
print(lowest, highest)1 7Lambda Functions
lambda creates a small, unnamed function in a single expression — no def, no return keyword,
and no statements, just one expression whose value is returned automatically. They're most useful as
a short throwaway function passed to something like sorted() or map().
square = lambda x: x * x
print(square(5))25Useful Built-in Functions
| Function | Example | Result |
|---|---|---|
| len() | len([1, 2, 3]) | 3 |
| range() | list(range(3)) | [0, 1, 2] |
| enumerate() | list(enumerate(["a", "b"])) | [(0, 'a'), (1, 'b')] |
| zip() | list(zip([1, 2], ["a", "b"])) | [(1, 'a'), (2, 'b')] |
| sorted() | sorted([3, 1, 2]) | [1, 2, 3] |
| map() | list(map(str, [1, 2, 3])) | ['1', '2', '3'] |
| filter() | list(filter(None, [0, 1, 2, ""])) | [1, 2] |
map() and filter() both return lazy iterators, not lists — nothing actually runs until you
consume them, which is why every example above wraps the call in list() to see the results.
filter(None, iterable) is a common idiom for dropping every falsy value (0, "", None,
False, empty containers) from an iterable in one line.
enumerate() pairs each item with its index, which avoids the error-prone pattern of manually
tracking a counter in a loop. It accepts a start argument for when counting should begin somewhere
other than 0:
words = ["kiwi", "fig", "banana"]
for i, word in enumerate(words, start=1):
print(i, word)1 kiwi
2 fig
3 bananasorted() accepts a key function that computes what to sort by, without changing the values
themselves — a frequent pattern for sorting by length, case-insensitively, or by a dict field:
sorted(["kiwi", "fig", "banana"], key=len) # ['fig', 'kiwi', 'banana']
sorted(["Bob", "ann", "Cy"], key=str.lower) # ['ann', 'Bob', 'Cy']zip() walks several iterables together and stops as soon as the shortest one runs out — it's the
standard way to loop over two related lists in lockstep instead of indexing into both by hand:
names = ["Ann", "Bo", "Cy"]
scores = [90, 85, 77]
for name, score in zip(names, scores):
print(name, score)Ann 90
Bo 85
Cy 77A few more that come up constantly: sum(), min(), max(), abs(), and round().
sum([1, 2, 3, 4]) # 10
max([1, 2, 3, 4]) # 4
min([1, 2, 3, 4]) # 1
abs(-7) # 7
round(3.14159, 2) # 3.14Exception Handling
try runs code that might fail; except catches a specific error type if it happens; else runs
only when no exception occurred; finally always runs last, whether or not there was an error —
useful for cleanup that must happen no matter what.
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Cannot divide by zero")
return None
else:
print("Division succeeded")
return result
finally:
print("Done attempting division")
print(safe_divide(10, 2))
print(safe_divide(10, 0))Division succeeded
Done attempting division
5.0
Cannot divide by zero
Done attempting division
NoneNotice that finally runs even though both calls hit a return earlier in the function — Python
finishes the finally block before the function actually hands the value back to its caller.
A single except can list several exception types in a tuple, which is handy when different kinds
of bad input should be handled the same way:
def parse_number(text):
try:
return int(text)
except (ValueError, TypeError) as e:
print(f"Could not parse {text!r}: {e}")
return 0
print(parse_number("42"))
print(parse_number("abc"))42
Could not parse 'abc': invalid literal for int() with base 10: 'abc'
0Use raise to signal your own error conditions, and a custom exception class (subclassing
Exception) when built-in exception types don't describe the problem precisely enough:
class InsufficientFundsError(Exception):
pass
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(f"Cannot withdraw {amount}, balance is {balance}")
return balance - amount
try:
withdraw(100, 150)
except InsufficientFundsError as e:
print(f"Transaction failed: {e}")Transaction failed: Cannot withdraw 150, balance is 100File Handling
open(path, mode) returns a file object. Wrapping it in a with block guarantees the file gets
closed automatically once the block ends — even if an exception is raised inside it — so you should
almost never need to call .close() yourself.
with open("notes.txt", "w") as f:
f.write("First line\n")
f.write("Second line\n")
with open("notes.txt", "r") as f:
content = f.read()
print(content, end="")
with open("notes.txt", "r") as f:
lines = f.readlines()
print(lines)
with open("notes.txt", "a") as f:
f.write("Third line\n")
with open("notes.txt", "r") as f:
for line in f:
print(line.strip())First line
Second line
['First line\n', 'Second line\n']
First line
Second line
Third lineThat example touches every common mode: "w" creates the file (or wipes it if it already existed),
"r" reads it back, "a" appends without disturbing what's already there, and looping directly over
the file object (for line in f) hands you one line at a time — each still carrying its trailing
"\n", which is why .strip() shows up so often alongside it. readlines() differs from .read()
in that it returns a list of individual lines rather than one long string.
Reading a file that doesn't exist raises FileNotFoundError, so file-reading code is often paired
with a try/except:
try:
with open("missing.txt", "r") as f:
data = f.read()
except FileNotFoundError:
data = ""
print("File not found — using default data")File not found — using default dataIf you're running these examples in a browser-based Python environment, keep in mind file operations usually go against an in-memory filesystem that resets between sessions rather than your real disk — the behavior is otherwise identical to a normal Python installation.
Try any snippet with your own values in the Python playground.