Python Program Structure
A Python program is built from a small set of structural pieces — modules, statements, expressions, indentation-defined blocks, functions, classes, and imports — assembled into files that can act as either runnable scripts or importable modules.
Before diving into individual language features, it helps to see the shape of a Python program as a whole: what a module is, how statements and expressions differ, how indentation replaces braces to define blocks, and how functions, classes, and imports fit together inside a single source file. This topic is about structure and organization, not any one feature in depth — functions, classes, and imports each get their own dedicated topics elsewhere.
The Module: Python's Basic Unit of Code
Every Python source file (a .py file) is a module. A module is simply a namespace containing the names — variables, functions, classes — defined at its top level. When you run a script directly, that file's module is the top-level program; when you import a file from elsewhere, that same file is treated as a reusable module. A Python program is typically composed of one or more modules working together, some written by you and some pulled from the standard library or third-party packages.
This file, by itself, is a complete module — it defines a module-level constant and a function.
# greetings.py — a small module
GREETING_LANGUAGE = "en"
def greet(name: str) -> str:
return f"Hello, {name}!"Statements versus Expressions
An expression is anything that evaluates to a value: 2 + 2, len("abc"), x > 0, or a function call like greet("Ada"). A statement is a complete instruction that performs an action — it may contain expressions, but a statement itself does not evaluate to a usable value. Assignment (x = 5), if, for, while, def, class, import, and return are all statements, not expressions.
Python does deliberately draw this line strictly in a few places: for example, assignment is a statement, not an expression, which is why x = (y = 5) is a SyntaxError. The walrus operator := (Python 3.8+) exists specifically to allow assignment inside an expression context where that's genuinely useful, like within a while condition.
2 + 2 # an expression — evaluates to 4
x = 2 + 2 # a statement — the whole line has no value; it performs a binding
print(x) # print(x) is an expression (calls print, which returns None);
# as a standalone line it's used as an "expression statement"Indentation Defines Blocks
Unlike C, C++, or Java, Python does not use braces {} to group statements into a block. Instead, a block is defined by a consistent level of indentation. Every compound statement (if, for, while, def, class, with, try, and others) ends its header line with a colon, and the statements that belong to it are indented one consistent level deeper.
Each colon introduces a new indented block. Dedenting back to a shallower level ends that block.
Indentation is not merely a style preference here — it is syntactically meaningful. Mixing tabs and spaces inconsistently, or indenting incorrectly, produces an IndentationError or a TabError rather than a stylistic warning. The universal convention (per PEP 8) is 4 spaces per indentation level, no tabs.
def classify(n: int) -> str:
if n > 0:
return "positive"
elif n < 0:
return "negative"
else:
return "zero"Functions as Building Blocks
A function, introduced with def, packages a reusable block of statements under a name, optionally taking parameters and returning a value. Functions are the primary unit of behavior reuse in Python, and — because functions are themselves first-class objects — they can be passed around, stored in data structures, and returned from other functions just like any other value.
Function definitions, parameters, default arguments, closures, and decorators each get their own dedicated topics; this section only establishes where functions fit in a program's structure.
def area_of_circle(radius: float) -> float:
return 3.14159 * radius ** 2
print(area_of_circle(2.0)) # 12.56636Classes as Building Blocks
A class, introduced with class, packages related data and behavior together into a reusable blueprint for objects. Where functions organize behavior, classes organize behavior together with state, and support additional structural tools like inheritance and encapsulation.
Classes and objects are covered in full in their own dedicated topic; this section only shows where class blocks sit within a module's overall structure.
class Circle:
def __init__(self, radius: float) -> None:
self.radius = radius
def area(self) -> float:
return 3.14159 * self.radius ** 2
c = Circle(2.0)
print(c.area()) # 12.56636Imports: Assembling a Program from Modules
The import statement brings names from another module into the current module's namespace, which is how programs are assembled from multiple files and libraries. import module gives access to module.name; from module import name brings name in directly.
By convention, all imports go at the very top of a file, before any other code, so a reader can see a module's dependencies at a glance. The Imports topic (elsewhere in this course) covers absolute vs relative imports, aliasing with as, and package structure in depth.
import math
from math import sqrt
print(math.sqrt(16)) # 4.0 — accessed through the module
print(sqrt(16)) # 4.0 — imported directly into this module's namespaceComments and Docstrings — Where They Fit
Comments (starting with #) and docstrings (string literals placed as the first statement in a module, function, or class) document structure without affecting runtime behavior in the ordinary case. A module-level docstring, placed as the very first line of the file, documents the module as a whole.
Comments and docstrings each have their own dedicated topic covering conventions, PEP 8 guidance, and help()/__doc__ in depth.
"""Utilities for working with basic 2D shapes."""
# Public API of this module
import math
def area_of_circle(radius: float) -> float:
"""Return the area of a circle with the given radius."""
return math.pi * radius ** 2The if __name__ == "__main__": Idiom
Every module has a built-in variable __name__. When a file is run directly (python file.py), Python sets that file's __name__ to the string "__main__". When the same file is instead imported from another module, __name__ is set to the module's own name (e.g. "greetings") instead. The idiom if __name__ == "__main__": lets a file define code that only runs when the file is executed directly — not when it's imported as a library.
Running python this_file.py prints "Hello, World!" because __name__ == "__main__" is True. Importing this file elsewhere (import this_file) defines greet and main but does not call main() automatically.
def greet(name: str) -> str:
return f"Hello, {name}!"
def main() -> None:
print(greet("World"))
if __name__ == "__main__":
main()Script versus Module — Same File, Two Roles
A "script" and a "module" are not different kinds of files — they are two different roles the same .py file can play. Any .py file can be run directly as a script (its top-level code executes immediately, __name__ is "__main__") or imported as a module by another file (its top-level code still executes once, but __name__ is the module's own name, and the importer typically only wants access to its defined functions/classes, not side effects). Well-structured files are written to behave sensibly in both roles, using the __name__ == "__main__" guard to separate "library code" from "what happens when you run me directly."
Organizing a Source File — Conventional Order
While Python does not enforce file layout beyond syntactic rules, a strong community convention (formalized loosely by PEP 8 and common practice) keeps files predictable:
Following this order is not required by the interpreter — Python doesn't care — but it makes files predictable to read, which is exactly the kind of convention Python's culture (readability counts) leans on heavily.
1. Module docstring (optional but recommended for non-trivial modules)
2. __future__ imports (rare, for specific compatibility features)
3. Standard-library imports
4. Third-party imports
5. Local/project imports
6. Module-level constants
7. Class and function definitions
8. if __name__ == "__main__": guard, calling a main() functionComplete Example
This single file demonstrates a docstring, an import, a module constant, a class, two functions, and the main-guard idiom — all the structural pieces from this topic in one place.
"""A tiny module demonstrating standard Python program structure."""
import math
PI_APPROX = 3.14159
class Circle:
"""A circle defined by its radius."""
def __init__(self, radius: float) -> None:
self.radius = radius
def area(self) -> float:
return math.pi * self.radius ** 2
def describe(circle: Circle) -> str:
"""Return a human-readable description of a circle's area."""
return f"A circle of radius {circle.radius} has area {circle.area():.2f}"
def main() -> None:
c = Circle(3.0)
print(describe(c))
if __name__ == "__main__":
main()Key Takeaways
A module is any .py file, treated as a namespace of names; a program is typically several modules working together.
Expressions evaluate to values; statements perform actions and may contain expressions but are not themselves values.
Indentation, not braces, defines blocks in Python — it is syntactically enforced, not stylistic.
Imports belong at the top of a file by convention, assembling a program from the standard library, third-party packages, and local modules.
if __name__ == "__main__": lets a file distinguish between being run directly and being imported, using the automatically-set __name__ variable.
"Script" and "module" describe roles a file plays, not two different file types.
def main(): print("Hello") main() # runs even when this file is only imported elsewhere def main(): print("Hello") if __name__ == "__main__": main()
Without the __name__ guard, importing this file as a module elsewhere triggers its side effects immediately, which is almost always unwanted for reusable code.
import os, sys, math # everything crammed on one line import math import os import sys
PEP 8 recommends one import per line for standard-library imports, making it easier to see, diff, and remove individual dependencies.
if True: x = 1 y = 2 # inconsistent indentation if True: x = 1 y = 2
All statements within the same block must share exactly the same indentation; inconsistent indentation raises IndentationError.
What is the difference between a statement and an expression? Give one example of each.
An expression evaluates to a value (e.g. 3 + 4 evaluates to 7). A statement performs an action and does not itself produce a usable value, even though it may contain expressions (e.g. x = 3 + 4 is an assignment statement; it binds a name but the statement as a whole has no value).
How does Python know where a block of code (like the body of an if statement) begins and ends?
Through indentation. The header line ends in a colon, and every statement indented one consistent level deeper belongs to that block; the block ends at the first line that dedents back to a shallower level.
What does __name__ equal when a file is run directly with python file.py? What does it equal when that same file is imported?
When run directly, __name__ is set to the string "__main__". When imported from another module, __name__ is set to the module's own name (typically derived from its filename, e.g. "file").
Is a .py file that gets imported by other files a different kind of file from one that gets run directly?
No — any .py file can act as either a script or a module; these are roles, not file types. The same file, run directly, executes its top-level code with __name__ == "__main__"; imported elsewhere, its top-level code still runs once (defining its functions/classes), but __name__ is the module's own name instead.
Why does PEP 8 convention put all imports at the top of a file?
So a reader (or tool) can see a module's full set of dependencies at a glance, without scanning the whole file. It also avoids surprising behavior from imports triggered deep inside conditional or function code.
What happens if you write inconsistent indentation within the same block — for example, mixing 3 spaces on one line and 4 spaces on the next within the same if body?
Python raises an IndentationError (or, if tabs and spaces are mixed, a TabError) at parse time — this is a syntax-level error, not a style warning, because indentation is how Python determines block membership.
If this file is saved as tools.py and another file runs import tools, what gets printed, and what does not?
"module loaded" is printed (top-level code always runs once on import), but 42 is NOT printed, because __name__ == "tools" during import, not "__main__", so the if block's body never executes.
Write a module-style .py file containing a function double(n) that returns n * 2, plus a main-guard block that calls and prints double(21) only when the file is run directly.
def double(n: int) -> int: return n * 2 def main() -> None: print(double(21)) if __name__ == "__main__": main()
Running this file directly prints 42; importing it elsewhere makes double and main available without printing anything.
What happens when this line is run, and why?
It raises a SyntaxError. Assignment in Python is a statement, not an expression, so it cannot be nested inside another expression like this. (The walrus operator y := 5 exists specifically to allow assignment within an expression context, but plain = cannot be used this way.)
List, in conventional order, the sections you'd expect to find from top to bottom in a well-organized Python source file.
Module docstring, then standard-library imports, then third-party imports, then local/project imports, then module-level constants, then class/function definitions, then an if __name__ == "__main__": guard calling a main() function.
def helper():
return 42
print("module loaded")
if __name__ == "__main__":
print(helper())x = (y = 5)