Python Syntax Basics: Statements vs Expressions and Blocks

An expression produces a value; a statement does something. How Python blocks, pass, comments, docstrings, keywords and naming conventions work.

  • Course: Python study plan
  • Module: Python and the interpreter
  • Kind: Lesson
  • Reading time: 13 min
  • Runtime: CPython 3.11

What is the difference between a statement and an expression in Python?

In Python, an expression is anything that produces a value — a literal, 2 + 3, a function call, x if x > 0 else -x — while a statement is an instruction such as an assignment, if, for, def or import. Any expression can stand alone as a statement, its value discarded, but a statement cannot appear where a value is expected; the walrus operator := is the assignment that is an expression.

Lesson

A Python file is a sequence of statements, some of which introduce indented blocks, some of which are expressions with a value, and a few of which — import, def, class — bind names that the rest of the file uses. This lesson names the parts so that the vocabulary is fixed before the track uses it: statement and expression, block and suite, comment and docstring, name and keyword, and the shape a well-formed program has from its first import to its last line.

Statements and expressions

An expression is anything that produces a value: 2 + 3, len(xs), name.upper(), x if x > 0 else -x, a function call, a literal. A statement is an instruction: assignment (x = 5), if, for, while, def, class, import, return, pass, del. Every expression can stand alone as a statement (its value is thrown away, which is what a bare xs.append(4) line does), but a statement cannot appear where an expression is expected — you cannot write y = if x: 1 else: 2, and you cannot put an assignment inside a function call's arguments. The one exception is the walrus operator, (n := len(xs)), an assignment expression added in 3.8 precisely for the cases where a value must be both tested and kept (Module 2).

total = 0                 # statement: assignment
for x in [1, 2, 3]:       # statement introducing a block
    total += x            # augmented assignment
print(total)              # expression statement: a call whose value is discarded

Blocks and indentation

A line ending in a colon — if, elif, else, for, while, def, class, try, except, finally, with, match, case — opens a block (the grammar calls it a suite). The block is every following line indented further than the opening line, and it ends when the indentation returns. Four spaces per level; the interpreter accepts any consistent width, and refuses a mix of tabs and spaces within a block with TabError.

def describe(n):
    if n % 2 == 0:
        kind = "even"
    else:
        kind = "odd"
    return f"{n} is {kind}"     # back at one level: part of the function, after the if

A block cannot be empty. When the body is intentionally nothing — a placeholder function, a class with no members yet — write pass, or a docstring, or ... (the Ellipsis literal, which some codebases use for stubs).

Line structure

A statement normally ends at the end of the line. It continues onto the next line automatically inside open brackets — (, [, { — which is the preferred way to break a long call or literal:

result = compute(
    first_value,
    second_value,
    scale=2.5,
)
names = [
    "ada", "grace",
    "linus",
]

The trailing comma before a closing bracket is legal and conventional: adding a line later changes one line in the diff instead of two. A backslash at the end of a line also continues it, but is fragile (a space after it is a syntax error) and rarely needed. Two statements may share a line separated by ;; style guides forbid it.

Comments and docstrings

# starts a comment to the end of the line; the interpreter ignores it. A docstring is a string literal that is the first statement of a module, function or class; it is not ignored — it is stored as __doc__ and shown by help():

def area(radius):
    """Return the area of a circle with the given radius."""
    return 3.141592653589793 * radius ** 2

Comments explain why — the reason for a non-obvious choice, the invariant a loop maintains — and docstrings explain what a function does for its caller. A comment that restates the code (# increment i) is noise. Module 15 returns to the conventions (PEP 257) that make docstrings useful to tools.

Names and keywords

A name is letters, digits and underscores, not starting with a digit, case-sensitive. Conventions carry meaning: snake_case for variables and functions, PascalCase for classes, UPPER_CASE for constants (which are ordinary names — Python has no const), a leading underscore _internal for "not part of the public interface", a trailing one class_ to dodge a keyword. The 35 keywords — if, else, for, while, def, class, return, import, from, as, in, is, not, and, or, None, True, False, lambda, yield, with, try, except, finally, raise, pass, break, continue, global, nonlocal, del, assert, async, await, elif — cannot be names. match and case are soft keywords: they act as keywords only at the start of a match statement, so a variable called match still works. Built-in function names like list, str, sum, id, input are not keywords, which is why shadowing them is possible and a classic bug: list = [1, 2] and the list() constructor is gone for the rest of the scope.

The shape of a program

A readable program has a fixed order:

"""Compute the average of the numbers on standard input."""
import sys                      # 1. imports: standard library, then third-party, then your own

PRECISION = 2                   # 2. module-level constants

def mean(values):               # 3. functions and classes
    return sum(values) / len(values)

def main():                     # 4. the entry point
    nums = [int(tok) for tok in sys.stdin.read().split()]
    print(f"{mean(nums):.{PRECISION}f}")

if __name__ == "__main__":      # 5. the guard, last
    main()

Imports at the top, so a missing dependency fails immediately and every reader knows what the file depends on; one function per job; the work under main(); the guard at the end. import sys binds the name sys to the module object; from math import sqrt binds just sqrt. from module import * binds an unknown set of names and is avoided outside the REPL.

Pitfalls

  • An empty block. if x: followed by a dedented line is a syntax error; use pass.
  • Shadowing a built-in: sum = 0 then sum(xs) fails with TypeError: 'int' object is not callable.
  • A stray ; or a backslash followed by a space.
  • Putting a docstring anywhere but first. A string in the middle of a function is a no-op expression statement.
  • Assuming UPPER_CASE is protected. It is a promise to readers, not to the interpreter.

Key takeaways

  • Expressions have values; statements do things; an expression can be a statement but not the reverse (except the walrus).
  • A colon opens a block; the block is the indented lines; pass fills an intentionally empty one.
  • Statements continue inside open brackets; prefer that to backslashes.
  • # comments are ignored; a first-statement string is a docstring, kept in __doc__.
  • Order a file: docstring, imports, constants, definitions, main(), the guard.

Common questions

What does pass do in Python?

pass is a statement that does nothing. A block cannot be empty, so pass fills one that is intentionally blank, such as a placeholder function or a class with no members yet. A docstring or ... (the Ellipsis literal) also works as a stub body.

What is the difference between a comment and a docstring in Python?

A # comment is ignored by the interpreter and should explain why the code is written as it is. A docstring is a string literal placed as the first statement of a module, function or class; it is kept in __doc__, shown by help(), and tells a caller what the code does.

How many keywords are there in Python?

Python 3.11 has 35 keywords, among them if, for, def, class, return, None, True, False, lambda, async and await, and none can be used as a name. match and case are soft keywords: they act as keywords only at the start of a match statement.

What are the naming conventions in Python?

PEP 8 uses snake_case for variables and functions, PascalCase for classes and UPPER_CASE for constants, which are ordinary names Python does not protect. A leading underscore marks a name as internal, and a trailing one, as in class_, avoids a clash with a keyword.

Why do I get TypeError: 'int' object is not callable?

A built-in function's name was rebound earlier in the scope, typically sum = 0 followed by sum(xs). Built-ins such as sum, list, str, id and input are ordinary names, not keywords, so assigning to one shadows the function until the scope ends. Rename the variable.

Exercises

Indentation check

Write a checker for the one rule the interpreter enforces before anything else. Read lines of source code until the end of input and check each line's leading whitespace: a line whose leading whitespace contains a tab is bad, and so is a line whose leading spaces are not a multiple of four. Blank lines (only whitespace) are ignored. Report the first bad line, or ok if there is none.

Input: zero or more lines of code. Output: ok, or line <n>: tab, or line <n>: indent <k> where k is the number of leading spaces (lines are numbered from 1).

def f():
   return 1

prints

line 2: indent 3

Count the kinds of line

Read source lines until the end of input and classify each one: blank if it contains only whitespace, comment if its first non-whitespace character is #, code otherwise (a line of code with a trailing comment is code). Print the three counts.

Input: zero or more lines. Output: three lines: code <n>, comment <n>, blank <n>.

# header
x = 1

y = 2  # trailing

prints

code 2
comment 1
blank 1

In this module: Python and the interpreter

← Running Python — the REPL, scripts, modules and the judge · Console input and output — the patterns every exercise uses →