Polyglot
Languages Python syntax
Python § syntax

Syntax

The syntax of Python is defined by the Python Language Reference (and the PEPs that extend it). Two design choices distinguish it from C-family languages: indentation delimits blocks (rather than braces), and expressions and statements are kept structurally separate (the conditional operator is a distinct expression form, the assignment statement is not an expression except in the narrow := walrus form). The grammar is intentionally small; readability is a stated design goal, codified in The Zen of Python (PEP 20). Reading idiomatic Python requires fluency with the indentation rule, the dynamic-typing implications throughout, and the substantial set of dunder (double-underscore) methods that govern protocol participation.

This page covers the surface a working programmer encounters routinely. The dedicated pages cover the major sub-grammars (types, classes, decorators, async, pattern matching).

A complete program

A typical script:

import sys
from pathlib import Path

def greet(name: str) -> str:
    return f"Hello, {name}."

def main() -> int:
    args = sys.argv[1:]
    if not args:
        names = ["world"]
    else:
        names = sorted(args)
    for name in names:
        print(greet(name))
    return 0

if __name__ == "__main__":
    sys.exit(main())

Several conventional features:

  • The #!/usr/bin/env python3 shebang (omitted here for brevity) admits direct execution on Unix.
  • The from … import … form for selective imports.
  • Type hints (name: str, -> str) are optional but conventional.
  • The if __name__ == "__main__": guard runs the body only when the file is executed as a script (not when imported as a module).

Execution:

python script.py alice bob

The reference interpreter is CPython; alternatives include PyPy (a JIT), Jython (JVM), and IronPython (.NET). Most code targets CPython; the others are uncommon in practice.

Source character set

Python source is interpreted as UTF-8 by default. The encoding may be declared on the first or second line of a file:

# -*- coding: utf-8 -*-

The declaration is rare in modern code; UTF-8 is the conventional and default encoding.

Identifiers may use any Unicode letter or digit, though in practice ASCII identifiers are conventional.

Identifiers and keywords

Python identifiers begin with a letter or underscore and continue with letters, digits, or underscores. The convention follows PEP 8:

  • snake_case for variables, functions, and modules.
  • CamelCase for classes.
  • SCREAMING_SNAKE_CASE for constants.
  • A single leading underscore (_value) marks a private convention (not enforced by the language).
  • A double leading underscore (__value) triggers name mangling in classes (rare).
  • A double leading and trailing underscore (__init__, __add__, __name__) marks dunder methods and attributes used by the language itself.

Reserved keywords (Python 3.13):

False    None    True    and     as       assert   async    await
break    class   continue def     del     elif     else     except
finally  for     from    global  if      import   in       is
lambda   nonlocal not    or      pass    raise    return   try
while    with    yield   match*  case*   _*

match, case, and _ are soft keywords — they are reserved only in pattern-matching contexts; they may be used as identifiers elsewhere.

Comments

The single comment form:

# A line comment, terminated by the end of the line.

x = 5  # An inline comment.

There is no block comment syntax. A multi-line comment-like effect is conventionally produced with a triple-quoted string left as an expression statement:

"""
This is technically a string expression, not a comment.
The interpreter discards it.
"""

The triple-quoted form is also the conventional docstring — a string literal at the start of a module, class, or function, used by introspection tools:

def square(n: int) -> int:
    """Return the square of n."""
    return n * n

def square_and_cube(n: int) -> tuple[int, int]:
    """
    Return the square and cube of n.

    Args:
        n: The base.

    Returns:
        A tuple (n**2, n**3).
    """
    return n * n, n * n * n

Docstrings are accessible via function.__doc__ and are consumed by help(), IDEs, documentation generators (Sphinx, mkdocs), and type checkers.

Indentation

Python uses indentation to delimit blocks rather than braces. The convention is four spaces; tabs are admitted but mixing is forbidden:

def factorial(n: int) -> int:
    if n <= 1:
        return 1
    else:
        result = 1
        for i in range(2, n + 1):
            result *= i
        return result

The colon : introduces a block; the body is the indented region following. Nested blocks are deeper indentations. Misalignment is a syntax error (IndentationError or TabError).

The mechanism enforces consistent visual structure but is occasionally surprising for newcomers from C-family languages. Code editors with Python support handle the indentation automatically; copying code from web pages occasionally produces tab/space mixtures that the interpreter rejects.

Statements and expressions

Python distinguishes statements (units of action) from expressions (units of evaluation). The principal statement forms:

expression                     # expression statement
identifier = expression        # assignment
identifier1, identifier2 =# tuple unpacking
identifier += expression        # augmented assignment

if condition:                   # conditional

elif condition2:

else:


for variable in iterable:        # iteration

else:                            # runs if loop completes without break


while condition:                 # while loop


try:                             # exception handling

except SomeException as e:

finally:


with context_manager as name:    # context manager


def name(params) -> return_type: # function definition


class Name(Bases):               # class definition


return expr
yield expr
raise expr
break
continue
pass
import module
from module import names
global name
nonlocal name
async def name(...): ...
async for / async with
match expr:                      # pattern matching (3.10+)
    case pattern:

Each statement occupies one logical line, separated by newlines. Multiple statements on one line are admitted (with ;) but rarely used:

x = 1; y = 2; z = 3   # legal but unconventional

For long lines, Python admits line continuation with \ or with implicit continuation inside (, [, or {:

total = (1 + 2 + 3 +
         4 + 5 + 6)

names = [
    "alice",
    "bob",
    "carol",
]

The implicit form (inside parentheses) is the conventional contemporary choice.

Type qualifiers and annotations

Python does not have C-family type qualifiers (const, volatile). The conventional immutability mechanism is by type — tuple instead of list, frozenset instead of set — or by convention (a leading underscore on a name).

Variable assignment is unrestricted: any name may be reassigned to any value at any time. There is no built-in const declaration.

The typing.Final annotation marks a name as not-to-be-reassigned, but this is checked only by type checkers (mypy, pyright), not by the runtime:

from typing import Final

PI: Final[float] = 3.14159
PI = 3.14  # type checker warns, but runtime accepts

Type hints

Python admits type hints — optional, opt-in static annotations on parameters, return values, and variables:

def greet(name: str, age: int = 0) -> str:
    return f"Hello, {name}, age {age}"

count: int = 0
names: list[str] = []

The type hints are not checked by the runtime — Python ignores them. They are consumed by type checkers (mypy, pyright, basedpyright), IDEs, and documentation tools. The full treatment is in Type hints.

The from __future__ import annotations (since 3.7) admits using type hints as strings (deferred evaluation), which avoids the runtime cost of evaluating type expressions and admits forward references.

The __name__ guard

A conventional Python file ends with:

if __name__ == "__main__":
    main()

The __name__ is set to "__main__" when the file is executed as a script and to the module’s name when it is imported. The guard admits a file that works both as a library (when imported, the body runs) and as a script (when executed, also runs main()).

A note on what Python admits

Several features the C-family takes for granted are absent from Python:

  • Block scope — Python has only function and module scope (with class as a third). Variables in a for loop are visible after the loop.
  • Pre/post increment (++x, x++) — Python uses x += 1. The ++ is two unary + operators, not a separate token.
  • Switch statement — replaced (since 3.10) by match/case for structural pattern matching.
  • do … while — Python has while True: plus break.
  • Strict typing — Python is dynamically typed; types are not checked at compile time.
  • Compile-time evaluation — Python has no constexpr; expressions are evaluated when the program runs.
  • Pointers — every name is a reference; the language hides the indirection.
  • Header filesimport is the conventional mechanism.

The combination — small grammar, strong dynamic typing, explicit-over-implicit conventions, the indentation rule — is the substance of Python’s identity. The language is deliberately easy to read and write at the cost of compile-time guarantees.