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 python3shebang (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_casefor variables, functions, and modules.CamelCasefor classes.SCREAMING_SNAKE_CASEfor 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
forloop are visible after the loop. - Pre/post increment (
++x,x++) — Python usesx += 1. The++is two unary+operators, not a separate token. - Switch statement — replaced (since 3.10) by
match/casefor structural pattern matching. do … while— Python haswhile True:plusbreak.- 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 files —
importis 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.