You know a language, and Python differs from it in a few places that matter more than the syntax.
This chapter and the ones that follow give a programmer’s
tour of Python: syntax and the built-in numbers, strings, and
None here, then containers, control flow,
functions, modules, classes, static typing, class attributes,
and object cleanup. The tour assumes you have programming
experience. Find supplementary information in the official language
documentation.
Python exists to improve your productivity. The language aims to aid you as much as possible. It tries to hinder you as little as possible. It does not impose arbitrary rules or force a particular set of features.
People often call Python a scripting language, but scripting languages tend to limit you, especially in the range of problems they can solve. Python is a programming language that also supports scripting. It is marvelous for scripting, and you may replace all your batch files, shell scripts, and simple programs with Python scripts.
The # denotes a comment that goes until the end
of the line, just like C++ and Java // comments. The Examples
explains the filename comment on a listing’s first line and the
#: output markers.
Python is clean to write. Your own code stays easy to read
long after you’ve written it. Indentation determines how
statements group into blocks. This script runs with
python if.py:
# if.py
response = "yes"
if response == "yes":
print("affirmative")
val = 1 # [1]
#: affirmative
print("continuing...") # [2]
#: continuing...
print(val)
#: 1A C/C++ if requires parentheses around the
conditional. Python makes them optional.
The conditional clause ends with a colon. A group of indented
statements follows: the “then” part of the if
statement. The print() function sends its arguments
to standard output, separated by spaces and followed by a
newline. The line at [1] assigns to a variable
named val. The statement at [2]
returns to the left margin, and that return ends the
if.
An indented block groups statements without creating a scope,
so val, assigned inside the if, stays
visible afterward, unlike a variable declared inside braces in
C++ or Java. New scopes come from functions, classes, modules,
and comprehensions, not from an if or a
for block.
Python binds a name only when the assignment runs. With any
answer other than "yes", the if skips
val = 1, and print(val) raises a
NameError.
# unbound_val.py
response = "no"
if response == "yes":
val = 1
try:
print(val) # type: ignore
except NameError as e:
print(e)
#: name 'val' is not definedPython skips the body of the if, so
val stays unbound. Indentation shows where the
assignment sits, not whether it runs.
The type checker sees that val = 1 cannot run
and reports an error on print(val), so
# type: ignore tells it the mistake is deliberate.
The try
and except catch the exception so the script
can print the exception’s message and finish.
Indenting can nest as deeply as you like. Four spaces per
level is the convention, and mixing tabs and spaces
inconsistently inside one block raises a
TabError.
C++ and Java programmers debate where braces go. In Python the indentation is the structure, so the language settles the question and taste plays no part. Python code from any two authors therefore lines up the same way, and that sameness is one of the main reasons for Python’s consistent readability.
A statement ends with its line, so it needs no terminating
semicolon. A semicolon’s one job is to separate two statements
that share a line. An open parenthesis, bracket, or brace keeps
the statement going across lines until it closes, and the
indentation of those continued lines is yours to choose. The
longer calls in tstrings.py wrap that
way.
A variable in Python is a name bound to an object, not a box that holds a value. Assignment binds a name. It does not copy. Python does not require you to declare a variable’s type, and one name can bind to objects of different types over its life. That freedom is dynamic typing.
# references.py
x = 10 # x names an int
x = "ten" # The same name now binds to a str
a = [1, 2, 3]
b = a # b binds to the same list, not a copy
b.append(4)
print(a) # The same object: a and b
#: [1, 2, 3, 4]
print(a is b) # Identical objects
#: True
c = a[:] # Copies the list, not its contents
print(a is c, a == c) # Different object, equal value
#: False True
nested = [[1], [2, 3]]
shallow = nested[:]
shallow[1].append(99)
print(nested) # The inner list is shared
#: [[1], [2, 3, 99]]Use == to ask whether two objects have equal
values. Use is to ask whether two names refer to
the same object. Reserve is for None
and other singletons.
a[:] is a shallow copy: it duplicates
the outer list but not the objects inside it.
nested and shallow still share the
same inner list, so shallow[1].append(99) changes
what nested sees too. The standard library’s
copy.deepcopy() duplicates the inner objects as
well.
Numbers, strings, and tuples are immutable:
operations produce new objects rather than changing the
original. Lists, dictionaries, and sets are mutable.
Mutability decides whether another name sees a change, as
a sees the 4 appended through
b in references.py.
You can assign several names at once, so a swap needs no temporary:
# multiple_assignment.py
a, b = 1, 2
a, b = b, a # Swap without a temporary
print(a, b)
#: 2 1
first, *rest = [10, 20, 30, 40]
print(first, rest)
#: 10 [20, 30, 40]*rest collects whatever remains. Containers
covers the general form.
Integers have unlimited precision, so they cannot overflow.
Underscores group digits for readability, so
10_000_000 is the same literal as
10000000. Floating point is the usual IEEE
double.
The operators are what you expect, with two worth noting:
/ on integers always produces a float,
and // is floor division (divide, then round down
to a whole number). The result’s type follows the operands, so
7.0 // 2 is 3.0. Floor division rounds
toward negative infinity, not toward zero, so
-7 // 2 is -4 where C and Java give
-3. The remainder follows from that, so
-7 % 2 is 1 in Python and
-1 in C. The sign of % matches the
divisor.
# arithmetic.py
print(7 / 2) # True division, always a float
#: 3.5
print(7 // 2) # Floor division
#: 3
print(7 % 2) # Remainder
#: 1
print(-7 // 2, -7 % 2) # Floors, not truncates toward zero
#: -4 1
print(2 ** 10) # Exponentiation
#: 1024
print(10 ** 30) # A 31-digit int, no overflow
#: 1000000000000000000000000000000
print(abs(-5), round(3.14159, 2))
#: 5 3.14
print(round(0.5), round(1.5), round(2.5)) # Ties go to even
#: 0 2 2
scores = [90, 0, 71, 0, 55]
print(sum(s > 60 for s in scores)) # True counts as 1
#: 2round() breaks a tie to the nearest even value,
so round(0.5) is 0 and both
round(1.5) and round(2.5) are
2, rather than rounding half away from zero as C’s
round() does. An f-string’s format spec rounds the
same way.
A bool is a subtype of int, so
True equals 1 and False
equals 0. Summing a sequence of comparisons
therefore counts how many are true.
The argument to sum() is a generator
expression, which hands over one value at a time
instead of building a list first.
Python has no ++ or --. Use
+= 1 and -= 1. Each arithmetic
operator has an augmented-assignment form: +=,
-=, *=, /=,
//=, %=, and **=.
# augmented.py
total = 0
total += 5 # Augmented assignment, like other languages
print(total)
#: 5
items = [1, 2]
alias = items
items += [3] # In place, so alias sees it
print(alias)
#: [1, 2, 3]
items = items + [4] # A new list, alias keeps the old one
print(alias, items)
#: [1, 2, 3] [1, 2, 3, 4]Augmented assignment on a mutable object changes it in place,
so every other name for it sees the change. For a list,
+= does what extend() does.
items = items + [4] instead builds a new list and
rebinds items to it, which leaves
alias bound to the old one. For an
int, both forms rebind the name, so
total += 5 behaves the way += does in
any other language.
Integers also support the bitwise and shift operators, each
with a matching augmented form (&=,
|=, ^=, <<=,
>>=). Binary literals, which start with
0b, make the bit patterns readable:
# bitwise.py
print(bin(0b1100 & 0b1010)) # AND, bits set in both
#: 0b1000
print(bin(0b1100 | 0b1010)) # OR, bits set in either
#: 0b1110
print(bin(0b1100 ^ 0b1010)) # XOR, bits set in exactly one
#: 0b110
print(bin(~0b1100)) # NOT, inverts every bit
#: -0b1101
print(bin(1 << 4)) # Left shift, same as 1 * 2 ** 4
#: 0b10000
# Right shift, same as 48 // 2 ** 2
print(bin(0b110000 >> 2))
#: 0b1100
flags = 0
flags |= 0b0010 # Set bits with the augmented form
flags |= 0b1000
print(bin(flags))
#: 0b1010The bin() function converts an integer to a
binary string for display. Because a Python integer grows as
wide as its value needs, ~ has no set number of
bits to flip. ~x produces -x - 1, the
value that flipping every bit gives in two’s complement.
bin() prints that as a sign followed by a
magnitude, so ~0b1100 reads as -0b1101
rather than a row of ones.
Python reserves one further operator, @ (with
@= to match), for matrix multiplication. The
built-in numeric types do not implement it, but array libraries
such as NumPy do. The same character in front of a
def or a class means something else.
That is decorator
syntax.
None is Python’s single “no value” object, like
null elsewhere. It is the default return value of a
function that returns nothing.
You can test any object in a boolean context. Numbers are
false when zero, containers are false when empty, and
None is always false. Everything else is true,
unless an object’s type says otherwise. That rule is
truthiness, and it lets you write
if items: instead of
if len(items) != 0:. A type says otherwise by
defining __bool__(). Without one, Python falls back
to __len__(), and that fallback is why an empty
container is false.
# truthiness.py
for value in [0, 1, "", "hi", [], [1], None]:
print(repr(value), "->", bool(value))
#: 0 -> False
#: 1 -> True
#: '' -> False
#: 'hi' -> True
#: [] -> False
#: [1] -> True
#: None -> False
class Bucket:
def __init__(self, count: int) -> None:
self.count = count
def __len__(self) -> int:
return self.count
print(bool(Bucket(0)), bool(Bucket(3)))
#: False True
if not []:
print("empty") # An empty list is falsy
#: emptyrepr() returns a value’s unambiguous
representation, so the empty string shows as '' and
not as blank. Bucket defines no
__bool__(), so bool() falls back to
its __len__(). Bucket(0) is false and
Bucket(3) is true.
Bucket is a small class. Its
count: int and -> int are type
hints. They state the types for the reader and the type
checker and change nothing about how the code runs.
and and or short-circuit and return
one of their operands, not a coerced boolean:
# or_fallback.py
# 'or' returns the first truthy operand
name = "" or "default"
print(name)
#: default
items = []
print(items and items[0]) # Stops at the falsy operand
#: []
count = 0
print(count or 10) # 0 is falsy, so the fallback wins
#: 10
print(10 if count is None else count) # Keeps the 0
#: 0and stops at its first falsy operand and returns
it, so Python skips items[0] on the empty list.
x or default is a common way to supply a fallback,
and it replaces every falsy x. A legitimate
0 or "" gets the fallback the same as
a missing value does. When zero or an empty string is a legal
value, test for None instead:
default if x is None else x. That is a conditional
expression.
Single or double quotes create strings. If you surround a string with double quotes, you can embed single quotes and vice versa:
# strings.py
print("That isn't a horse")
#: That isn't a horse
print('You are not a "Viking"')
#: You are not a "Viking"
print("""
You're just pounding two
coconut halves together.
""")
#:
#: You're just pounding two
#: coconut halves together.
#:
print('''
"Oh no!" He exclaimed.
"It's the blancmange!"
''')
#:
#: "Oh no!" He exclaimed.
#: "It's the blancmange!"
#:
print(r'c:\python\lib\utils')
#: c:\python\lib\utilsPython’s name comes not from the snake but from the Monty Python comedy troupe, of which the language creator Guido van Rossum is a fan. Examples often include Python-esque references.
The triple-quote syntax quotes everything, including newlines. That suits any block of literal text, such as an embedded template, a SQL query, or a chunk of HTML. You can write it out in full without escaping line breaks.
In an ordinary string, a backslash starts an escape sequence,
as in C and Java. \n is a newline and
\t is a tab. The r right before a
string means “raw”: Python takes each backslash literally, as a
single character. Without the r, the last literal
in strings.py is a syntax
error. \u starts a Unicode escape, and
tils is not the four hex digits it requires. One
limit remains. A raw string cannot end with a single backslash,
because a raw string’s backslash still escapes the closing
quote.
Strings are immutable sequences with a large set of methods.
Slicing
selects a range of characters, and in tests for a
substring:
# string_methods.py
s = " Hello, World "
print(s.strip())
#: Hello, World
print(s.strip().lower())
#: hello, world
print("World" in s)
#: True
print("a,b,c".split(","))
#: ['a', 'b', 'c']
print("-".join(["2024", "06", "15"]))
#: 2024-06-15
print("ababab".replace("a", "X"))
#: XbXbXb
print(s.strip()[0:5])
#: HelloString methods return new values rather than changing the
original. replace() replaces every occurrence, as
XbXbXb shows; an optional third argument caps how
many it replaces.
strip() and its one-sided forms
lstrip() and rstrip() take an optional
argument, but it is a set of characters, not a prefix or suffix.
"test_setup".lstrip("test_") removes every leading
t, e, s, and
_, leaving "up". To remove one exact
piece from an end, use removeprefix() or
removesuffix().
"test_setup".removeprefix("test_") is
"setup", and
"notes.txt".removesuffix(".txt") is
"notes".
+ joins two strings, and it converts
nothing:
# concatenate.py
total = 7
try:
print("total: " + total) # type: ignore
except TypeError as e:
print(e)
#: can only concatenate str (not "int") to str
print("total: " + str(total))
#: total: 7Java and JavaScript turn the 7 into text and
join it. Python requires two strings, so you write the
conversion, either with str() or inside an
f-string.
Modern Python uses f-strings. Prefix the string with
f and put expressions in curly braces. F-strings
are readable and fast:
# fstrings.py
name = "Alice"
score = 91.5
print(f"{name} scored {score}")
#: Alice scored 91.5
print(f"{name} scored {score:.0f}%")
#: Alice scored 92%
print(f"{name!r} has {len(name)} letters")
#: 'Alice' has 5 letters
total = 7
print(f"{total = }") # Useful for debugging
#: total = 7
print(f"|{name:>10}|{score:<8.1f}|")
#: | Alice|91.5 |The format spec after a colon controls width, precision, and
alignment. > right-aligns and <
left-aligns within the given width. A !r on the
expression, as in {name!r}, formats the value with
repr() instead of str(). An
= after the expression, as in
{total = }, prints the expression’s text, the
= with any spaces you put around it, and then the
value. With no format spec the value appears as its
repr(), so f"{name=}" produces
name='Alice'. Use = to label values in
your own exploring and debugging code.
Existing code also carries two older styles: C’s
printf() syntax, as in
"val: %d" % val, and the str.format()
method, as in "val: {}".format(val). Both still
work. str.format() shares the f-string’s format
mini-language, while the % form has its own,
inherited from C’s printf(). F-strings replaced
them.
An f-string produces a finished str, deciding
how each value becomes text before anything else sees it. A
t-string (Python 3.14, PEP 750) produces a
Template instead, which keeps the literal pieces
and the interpolated values apart for a consumer to
assemble.
The reason to care is safety. A consumer that receives the parts separately knows which text came from the program and which came from a value, so it can quote, escape, or reject the values before they become part of the result.
# tstrings.py
from string.templatelib import Interpolation, Template
name = "Alice"
score = 91.5
message: Template = t"{name} scored {score:.0f}%"
print(message.strings)
#: ('', ' scored ', '%')
print([piece.expression
for piece in message.interpolations])
#: ['name', 'score']
def shout(template: Template) -> str:
parts: list[str] = []
for piece in template:
if isinstance(piece, Interpolation):
parts.append(
format(piece.value, piece.format_spec))
else:
parts.append(piece.upper())
return "".join(parts)
print(shout(message))
#: Alice SCORED 92%
def safe(template: Template) -> str:
parts: list[str] = []
for piece in template:
if isinstance(piece, Interpolation):
value = str(piece.value)
if "'" in value:
raise ValueError(
f"unsafe value: {value!r}")
parts.append(value)
else:
parts.append(piece)
return "".join(parts)
print(safe(t"Hello, {name}"))
#: Hello, Alice
trouble = "Bob'; rm -rf /"
try:
safe(t"Hello, {trouble}")
except ValueError as e:
print(e)
#: unsafe value: "Bob'; rm -rf /"Iterating over a Template produces the pieces in
order, each either a str the author typed or an
Interpolation carrying a value. An
Interpolation also remembers the source text of the
expression that produced it, and piece.expression
reports that text. Collecting every
piece.expression above uses a list
comprehension, which has the same kind of for
clause as the generator expression in arithmetic.py but builds a
list. Iteration skips empty literal strings, so the leading
'' in message.strings does not reach
the loop. That skipping is why a consumer cannot assume that
literals and interpolations alternate.
shout() uppercases the literal text and leaves
the interpolated values in their original case. No amount of
work on a finished f-string could do that reliably, because the
finished string no longer says which characters came from where.
safe() puts that separation to work. It passes the
literal text through unchanged, and it rejects an interpolated
value that contains a single quote, the way trouble
does above. An f-string finishes assembling the result, quote
and all, before any code can object. Uppercasing and rejecting
are small demonstrations. Composite
and Interpreter builds a full query from the parts
the same way.
Keep using f-strings for ordinary output. Use a t-string only
when a consumer must inspect, escape, or reject the interpolated
values before they become part of the result, the way
safe() does above.
Use snake_case for variables, functions,
methods, and file names: lower case with words separated by
underscores, as in this_is_snake_case.
For a constant, use all uppercase letters, as in
THIS_IS_A_CONSTANT.
Class names are CapWords (Pascal cased): every
word, including the first, begins with a capital letter, and no
underscores separate them. For example:
ThisIsMyClass.
When users call a class the way they call a function, that
class may use snake_case instead, so the name reads
as the action the call performs and the caller treats it as a
function. The standard library names
contextlib.suppress,
functools.partial, and the built-ins
property and staticmethod that way.
Name every other class CapWords.
PEP 8 covers style issues. Tools such as ruff point out violations and fix many of them automatically.
This chapter’s solutions give a hint, usually the shape of the code, and a full answer for each exercise.
references.py, add a line
after c = a[:] that appends 99 to
c. Print a and c and
confirm that c changed and a still
holds [1, 2, 3, 4], then explain why
b.append(4) earlier did change what a
sees, but appending to c does not.truthiness.py, add an
empty dictionary {} and a dictionary with one entry
to the list of test values. Predict what bool()
reports for each before running it, then check your
prediction.fstrings.py, add a line
that formats score with two decimal places instead
of zero, using {score:.2f} in place of
{score:.0f}%, and a second line using the debug
specifier, f"{score = }".augmented.py defines
total and bitwise.py defines
flags. Rename them to totalSum and
flagBits, then to TOTAL_SUM and
FLAG_BITS. Every version runs. Using Naming Conventions, say what each
form signals to a reader who did not write the code, and which
of the three a linter flags.tstrings.py, write a third
consumer, quoted(template), that wraps every
interpolated value in single quotes and leaves the literal text
alone, then print quoted(message). Explain why you
cannot post-process an f-string the same way.-9 / 4 and -9 % 4 using integer
math, then what Python prints for -9 // 4 and
-9 % 4. Run print(-9 // 4, -9 % 4) and
check. State the rule that predicts the sign of the result of
%.