Types and Variables
Every value in Python is an object with a type, and every variable is a name bound to one of those objects. Getting that model right early explains almost every surprising behaviour you will meet later.
Numbers
Section titled “Numbers”int — arbitrary precision
Section titled “int — arbitrary precision”Python integers have no fixed width. They grow to whatever memory allows, so there is no overflow and no int64 ceiling.
2 ** 1000# => a 302-digit number, exactly correct
import syssys.maxsize # => 9223372036854775807 — the largest *index*, not the largest intInteger literals accept underscores for readability and prefixes for other bases:
1_000_000 # => 10000000b1010 # binary => 100o755 # octal => 4930xFF # hex => 255Division is where Python differs most from C-family languages:
7 / 2 # => 3.5 true division, ALWAYS returns a float7 // 2 # => 3 floor division-7 // 2 # => -4 floors toward negative infinity, not toward zero7 % 3 # => 1-7 % 3 # => 2 the result takes the sign of the divisordivmod(7, 2) # => (3, 1)2 ** 10 # => 1024float — IEEE 754 doubles
Section titled “float — IEEE 754 doubles”Floats are 64-bit binary doubles, with all the usual consequences.
0.1 + 0.2 # => 0.300000000000000040.1 + 0.2 == 0.3 # => FalseThis is not a Python bug; 0.1 has no exact binary representation. Compare floats with a tolerance:
import mathmath.isclose(0.1 + 0.2, 0.3) # => Truemath.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)Special values and useful helpers:
float("inf"), float("-inf"), float("nan")math.isnan(float("nan")) # => Truefloat("nan") == float("nan") # => False — NaN equals nothing, including itself
round(2.675, 2) # => 2.67 (2.675 is really 2.67499...)round(0.5) # => 0 banker's rounding: ties go to evenround(1.5) # => 2For money and anywhere exactness matters, use decimal; for exact ratios use fractions:
from decimal import DecimalDecimal("0.1") + Decimal("0.2") # => Decimal('0.3')
from fractions import FractionFraction(1, 3) + Fraction(1, 6) # => Fraction(1, 2)complex
Section titled “complex”Built in, with a j suffix for the imaginary part. Rarely needed outside numeric work, but it exists.
z = 3 + 4jz.real, z.imag # => (3.0, 4.0)abs(z) # => 5.0bool is a subclass of int. True is 1 and False is 0, and they behave as such in arithmetic.
True + True # => 2sum([True, False, True]) # => 2 — a handy way to count matchesisinstance(True, int) # => TrueStrings
Section titled “Strings”str is an immutable sequence of Unicode code points. Not bytes, not UTF-8 — code points.
s = "café"len(s) # => 4 characters, regardless of encodings[3] # => 'é'Literals
Section titled “Literals”'single'"double" # identical; pick one per project"""triple quotedspans lines""""implicit" " concatenation" # => 'implicit concatenation'r"C:\new\table" # raw: backslashes are literal — essential for regexb"bytes literal" # a bytes object, not a strImmutability
Section titled “Immutability”You cannot modify a string in place. Every “modification” builds a new object.
s = "hello"s[0] = "H" # TypeError: 'str' object does not support item assignments = "H" + s[1:] # => 'Hello' — a new stringIndexing and slicing
Section titled “Indexing and slicing”Slicing is s[start:stop:step] — start inclusive, stop exclusive. Negative indices count from the end. Out-of-range slices clamp instead of raising.
s = "abcdefg"s[0] # => 'a's[-1] # => 'g's[1:4] # => 'bcd's[:3] # => 'abc's[3:] # => 'defg's[::2] # => 'aceg's[::-1] # => 'gfedcba' the idiomatic reverses[100] # IndexErrors[100:] # => '' slices never raiseThe same slicing rules apply to lists, tuples, and every other sequence.
Methods worth knowing
Section titled “Methods worth knowing”All of these return new strings; none mutate.
" pad ".strip() # => 'pad' (also lstrip, rstrip)"a,b,c".split(",") # => ['a', 'b', 'c']"a b c".split() # => ['a', 'b', 'c'] — no arg splits on any whitespace run"-".join(["a", "b"]) # => 'a-b'"Hello".lower() # => 'hello' (also .upper(), .casefold())"hello world".title() # => 'Hello World'"hello".replace("l", "L") # => 'heLLo'"hello".startswith("he") # => True (also endswith; both accept a tuple)"hello".find("z") # => -1"hello".index("z") # ValueError"a1".isdigit(), "ab".isalpha(), " ".isspace()"file.txt".removesuffix(".txt") # => 'file' (3.9+; also removeprefix)"line\n".splitlines() # => ['line']"%05.2f" % 3.14159 # legacy formatting, still seenf-strings
Section titled “f-strings”Introduced in 3.6, f-strings are the default way to build strings. The expression inside the braces is evaluated at runtime.
name, count = "Ada", 3f"{name} has {count} items"f"{count * 2}" # any expressionf"{name!r}" # => "'Ada'" — !r calls repr(), !s calls str()f"{3.14159:.2f}" # => '3.14'f"{1234567:,}" # => '1,234,567'f"{42:>8}" # right-align in 8 columnsf"{42:08b}" # => '00101010' binary, zero-paddedf"{0.256:.1%}" # => '25.6%'f"{name=}" # => "name='Ada'" — self-documenting, 3.8+Format specs are shared with format() and str.format(): [[fill]align][sign][#][0][width][,][.precision][type].
Python 3.12 relaxed f-string parsing (PEP 701): you may now reuse the same quote character inside, nest arbitrarily, and include backslashes.
# Valid only on 3.12+f"{d["key"]}"f"{'\n'.join(items)}"str vs bytes
Section titled “str vs bytes”str is text. bytes is binary data. Converting between them requires an encoding, and Python refuses to guess.
text = "café"data = text.encode("utf-8") # => b'caf\xc3\xa9' — str -> byteslen(data) # => 5 bytes for 4 charactersdata.decode("utf-8") # => 'café' — bytes -> str
b"abc" + "def" # TypeError: can't concat str to bytesBytes are a sequence of integers 0–255:
data[0] # => 99 (an int, not a 1-byte object)data[0:1] # => b'c'The rule of thumb, sometimes called the Unicode sandwich: decode bytes to str at the boundary of your program, work in str internally, encode back to bytes on the way out. Files opened in text mode do this for you:
open("f.txt", "r", encoding="utf-8") # yields stropen("f.bin", "rb") # yields bytesNone is the single instance of NoneType. It means “no value” and is what a function returns when it has no return.
def f(): pass
f() is None # => TrueTest with is, never ==:
if value is None: ...if value is not None: ...Variables are names, not boxes
Section titled “Variables are names, not boxes”A Python variable is a name bound to an object. Assignment never copies a value; it points a name at an object.
a = [1, 2, 3]b = a # b names the SAME listb.append(4)a # => [1, 2, 3, 4]Rebinding a name affects only that name:
a = [1, 2, 3]b = ab = [9] # b now names a different lista # => [1, 2, 3]This is why mutable objects (list, dict, set, most class instances) behave differently from immutable ones (int, float, str, tuple, frozenset, bytes). With an immutable object there is no way to observe sharing, so you never notice.
x = 5y = xy += 1 # rebinds y to a new int objectx # => 5id() shows an object’s identity (in CPython, its memory address):
a = [1]b = aid(a) == id(b) # => TrueMultiple assignment and unpacking
Section titled “Multiple assignment and unpacking”a, b = 1, 2a, b = b, a # swap; no temporary neededx = y = z = 0 # all three name the same object
first, *rest = [1, 2, 3, 4] # first=1, rest=[2, 3, 4]*init, last = [1, 2, 3, 4] # init=[1, 2, 3], last=4(a, b), c = (1, 2), 3 # nestedfor i, (k, v) in enumerate(d.items()): ...Truthiness
Section titled “Truthiness”Every object is usable in a boolean context. These are falsy — everything else is truthy:
None,False- Zero of any numeric type:
0,0.0,0j,Decimal(0) - Empty containers:
"",[],(),{},set(),range(0) - Objects whose
__bool__returnsFalse, or whose__len__returns0
if items: # idiomatic "not empty" ...if not name: # idiomatic "empty or None" ...and and or short-circuit and return one of the operands, not a bool:
0 or "default" # => 'default'"a" and "b" # => 'b'None or 0 or [] # => [] — the last operand when all are falsyname = user_input or "anonymous" # common default idiomis vs ==
Section titled “is vs ==”==asks are these equal? and calls__eq__.isasks are these the same object? and compares identities.
a = [1, 2]b = [1, 2]a == b # => True same contentsa is b # => False two distinct list objectsUse is only for singletons: None, True, False, and sentinel objects you create yourself.
MISSING = object() # a unique sentinel
def get(d, key, default=MISSING): if default is MISSING: ...Inspecting types
Section titled “Inspecting types”type(3) # => <class 'int'>type(3) is int # => True — exact type, no subclasses
isinstance(True, int) # => True — subclasses countisinstance(x, (int, float)) # a tuple means "any of these"isinstance(x, int | float) # 3.10+ union syntax works too
issubclass(bool, int) # => TruePrefer isinstance over type(x) == ...; it respects inheritance, which is almost always what you want. Better still, prefer duck typing — try the operation and handle failure — over type checks. See EAFP vs LBYL.
Conversions
Section titled “Conversions”Constructors do the converting, and they raise rather than guess:
int("42") # => 42int("1010", 2) # => 10 parse with an explicit baseint("ff", 16) # => 255int("3.9") # ValueError — int() does not parse floats from stringsint(3.9) # => 3 truncates toward zerofloat("1e3") # => 1000.0str(42) # => '42'list("abc") # => ['a', 'b', 'c']bool("False") # => True — any non-empty string is truthyKey points
Section titled “Key points”intis unbounded;/always returns a float;//and%floor.- Floats are IEEE doubles — compare with
math.isclose, useDecimal("...")for money. boolis anintsubclass, which makessum(conditions)a counting idiom.strholds code points;bytesholds octets;encode/decodebridge them, always with an explicit encoding.- Strings are immutable —
joina list rather than+=in a loop. - Variables are names bound to objects; assignment never copies.
==compares values,iscompares identity — useisonly forNoneand sentinels.or/andreturn operands, not booleans, and short-circuit.