I want to start with something that looks completely useless. Tell me what you think this does:
def validate_product(product: dict) -> list[str]:
passNothing. The function is empty. It doesn't even return anything — I'd get None back.
Correct on both counts. pass is a statement that does exactly nothing. Python requires that certain blocks have at least one statement: if, for, def, class — the parser demands a body. When you have nothing to put there yet, pass satisfies the parser without changing behavior.
I've seen it before but always thought it was strange. Why not just leave the block empty?
Because Python's syntax doesn't allow empty blocks. def validate_product(): with nothing after the colon is a SyntaxError before the code even runs. pass is the minimum viable body — it says "this block exists, it just doesn't do anything yet." Three situations where it genuinely earns its place.
What are the three situations?
First: sketching a design. You want to define all functions in a module — names, signatures, return types — before implementing any of them. pass lets you run the file without errors:
def validate_product(product: dict) -> list[str]: pass
def route_order(order: dict) -> str: pass
def generate_skus(prefix: str, start: int, count: int) -> list[str]: passSecond: a conditional branch that genuinely requires no action:
def process_order(order: dict) -> None:
match order.get("status"):
case "pending":
send_to_queue(order)
case "cancelled":
pass # cancelled orders need no action — intentionally ignored
case "shipped":
update_tracking(order)The pass there is actually saying something. Without it, you might wonder if the cancelled branch was overlooked. With it, it's clear the decision was to do nothing.
Exactly. A pass in a live branch is documentation in code form — "this case was considered and the decision was nothing." Third situation: exception handling where you intentionally swallow an error:
try:
quantity = int(raw_input)
except ValueError:
pass # leave quantity at its default if conversion failsThe whole language feature is formalized nothingness. I sort of respect that.
Python respects nothingness deeply. There's None, there's pass, and there's ... — the ellipsis literal, which does the same job in type stubs and abstract interfaces. Three ways to say "nothing here." Very thoughtful emptiness.
The ellipsis is valid as a function body? ... on its own line?
In type stubs and protocols, yes — it signals "declared but not implemented." For everything else, use pass. Now — once you fill that pass in, what governs how the code looks?
PEP 8? I've heard the name but never read it.
Python Enhancement Proposal 8. The style guide the language designers wrote. Not enforced by the interpreter. Enforced by every professional team, every open-source project worth contributing to, and by your future self when you open a six-month-old file. Core rules that apply every day:
# PEP 8: correct
def validate_product(product: dict) -> list[str]:
errors = []
name = product.get("name", "")
if not name:
errors.append("name is required")
return errors
# PEP 8: violations throughout
def validate_product( product:dict )->list[str]:
errors=[]
name=product.get( 'name','' )
if not name : errors.append( 'name is required' )
return errorsBoth work. The second is uncomfortable to read.
That discomfort is the point of PEP 8. Code is read ten times more than it's written. When everything follows the same conventions, your eye knows where to look. You read the logic, not the formatting. When conventions are inconsistent, you decode the layout before reaching the meaning. The core rules: four spaces of indentation, never tabs. Lines no longer than 79 characters. Two blank lines between top-level functions. Spaces around operators, no spaces around = in keyword arguments.
It's like the warehouse bin labeling system Diane instituted. Same format on every bin — item code, description, zone, max capacity. Before that, finding anything was a fifteen-minute exercise.
That's the analogy exactly. PEP 8 is the bin labeling standard for Python code. It doesn't make the bins hold more — it makes finding the right bin fast and reliable.
What about naming conventions? I've seen camelCase, snake_case, PascalCase — Python seems to have opinions.
Strong opinions. Functions and variables: snake_case — lowercase words, underscores between them. validate_product, error_messages, is_valid. Classes: PascalCase — each word capitalized, no separators. ProductValidator, OrderProcessor. Constants: UPPER_SNAKE_CASE. MAX_PRICE, DEFAULT_WAREHOUSE.
MAX_SKU_LENGTH = 20 # constant
class ProductValidator: # class
def validate_product(self, product: dict) -> list[str]: # method
error_messages = [] # variable
return error_messagesSo if I write def ValidateProduct(), every Python developer who sees it immediately knows I came from JavaScript or Java.
Or C#. The naming conventions are tribal markers. They signal that you've spent time in the community and understand the idioms. The next person on the codebase won't have to adjust their mental model when they hit your code.
Let me bring this back to validate_product. Four checks: name non-empty, SKU format, price positive, stock non-negative. Each check appends to errors or doesn't. Return errors at the end.
Clean structure. The SKU check is the interesting one — "starts with a letter and contains a hyphen." You could combine those into one condition or check them separately. Which is more readable?
Separate checks. If I combine with and, a caller gets one message covering two different problems. Separate checks give them "SKU must start with a letter" and "SKU must contain a hyphen" as distinct errors. Diane would rather know exactly which rule failed.
One error per violated rule. Granular feedback is more actionable than combined feedback. And the sentinel default for stock — use -1, not 0, because 0 is a valid stock level. A missing stock key needs to trigger an error, not silently pass.
If I used 0 as the default and stock was missing, the check if stock < 0 would pass silently and Diane would get a product with no stock key in her catalog. Using -1 means the error fires. That's the kind of detail that separates code that works from code that's correct.
Tomorrow: Week 4 begins. Maya's script is over three hundred lines now — routing logic tangled with reporting logic tangled with validation logic. It all works. It's also a maintenance problem. Next week is about how Python thinks about organizing code. Not one file, but many. Not one namespace, but separate modules with clear responsibilities.
So validate_product might eventually live in its own validation.py that other parts of the codebase import from?
Exactly. The function you write today is ready for that move. Clean signature, docstring, no global state, returns something useful. Well-styled code is portable code — you can pick it up and drop it into a module without rewriting anything around it.
Let's write it. Four checks, one error per rule, docstring first, snake_case throughout. Diane's going to use this on every product that comes through the intake form.
pass is a statement that does nothing — it is a syntactic placeholder required when Python's grammar demands a block body but you have no code to put there yet. pass appears in three contexts:
# Design scaffolding
def validate_product(product: dict) -> list[str]:
pass # implements nothing — SyntaxError without pass
# Intentional no-op
match order.get("status"):
case "cancelled":
pass # cancelled orders need no action
case "shipped":
update_tracking(order)
# Exception silencing
try:
qty = int(raw_input)
except ValueError:
pass # leave qty at default if conversion fails... (Ellipsis): In type stubs and abstract interfaces, ... is an idiomatic alternative to pass. Same semantics, different convention: pass in implementation code, ... in protocol/stub declarations.
PEP 8 — key rules:
| Rule | Correct | Incorrect |
|---|---|---|
| Indentation | 4 spaces | tabs or 2 spaces |
| Line length | ≤79 chars (99 by team agreement) | scrolling horizontally |
| Top-level spacing | 2 blank lines between functions | 0 or 1 blank lines |
| Operators | x = 1, a + b | x=1, a+b |
| Keyword args | f(key=value) | f(key = value) |
| Functions/variables | snake_case | camelCase |
| Classes | PascalCase | snakecase |
| Constants | UPPER_SNAKE_CASE | lowercase |
Pitfall 1: Forgetting pass entirely. An empty if, for, def, or class body causes a SyntaxError. The block must contain at least one statement.
Pitfall 2: Using pass instead of return or raise. pass does nothing — execution continues to the next statement. If you need the function to exit early or signal an error, you need return or raise, not pass.
Pitfall 3: Silencing exceptions broadly. except Exception: pass hides every error including unexpected ones. Always catch the most specific exception you expect, and only silence it when you have a safe default.
pass has a modern equivalent in async contexts: async def f(): pass — but async functions returning None are fine, so pass there is purely scaffolding.
Linters enforce PEP 8 automatically. ruff is the current standard — it replaces flake8, isort, and parts of pylint with a single fast tool. Running ruff check . in any Python project will flag PEP 8 violations and many common bugs. Most teams configure it as a pre-commit hook so style violations never reach code review.
Sign up to write and run code in this lesson.
I want to start with something that looks completely useless. Tell me what you think this does:
def validate_product(product: dict) -> list[str]:
passNothing. The function is empty. It doesn't even return anything — I'd get None back.
Correct on both counts. pass is a statement that does exactly nothing. Python requires that certain blocks have at least one statement: if, for, def, class — the parser demands a body. When you have nothing to put there yet, pass satisfies the parser without changing behavior.
I've seen it before but always thought it was strange. Why not just leave the block empty?
Because Python's syntax doesn't allow empty blocks. def validate_product(): with nothing after the colon is a SyntaxError before the code even runs. pass is the minimum viable body — it says "this block exists, it just doesn't do anything yet." Three situations where it genuinely earns its place.
What are the three situations?
First: sketching a design. You want to define all functions in a module — names, signatures, return types — before implementing any of them. pass lets you run the file without errors:
def validate_product(product: dict) -> list[str]: pass
def route_order(order: dict) -> str: pass
def generate_skus(prefix: str, start: int, count: int) -> list[str]: passSecond: a conditional branch that genuinely requires no action:
def process_order(order: dict) -> None:
match order.get("status"):
case "pending":
send_to_queue(order)
case "cancelled":
pass # cancelled orders need no action — intentionally ignored
case "shipped":
update_tracking(order)The pass there is actually saying something. Without it, you might wonder if the cancelled branch was overlooked. With it, it's clear the decision was to do nothing.
Exactly. A pass in a live branch is documentation in code form — "this case was considered and the decision was nothing." Third situation: exception handling where you intentionally swallow an error:
try:
quantity = int(raw_input)
except ValueError:
pass # leave quantity at its default if conversion failsThe whole language feature is formalized nothingness. I sort of respect that.
Python respects nothingness deeply. There's None, there's pass, and there's ... — the ellipsis literal, which does the same job in type stubs and abstract interfaces. Three ways to say "nothing here." Very thoughtful emptiness.
The ellipsis is valid as a function body? ... on its own line?
In type stubs and protocols, yes — it signals "declared but not implemented." For everything else, use pass. Now — once you fill that pass in, what governs how the code looks?
PEP 8? I've heard the name but never read it.
Python Enhancement Proposal 8. The style guide the language designers wrote. Not enforced by the interpreter. Enforced by every professional team, every open-source project worth contributing to, and by your future self when you open a six-month-old file. Core rules that apply every day:
# PEP 8: correct
def validate_product(product: dict) -> list[str]:
errors = []
name = product.get("name", "")
if not name:
errors.append("name is required")
return errors
# PEP 8: violations throughout
def validate_product( product:dict )->list[str]:
errors=[]
name=product.get( 'name','' )
if not name : errors.append( 'name is required' )
return errorsBoth work. The second is uncomfortable to read.
That discomfort is the point of PEP 8. Code is read ten times more than it's written. When everything follows the same conventions, your eye knows where to look. You read the logic, not the formatting. When conventions are inconsistent, you decode the layout before reaching the meaning. The core rules: four spaces of indentation, never tabs. Lines no longer than 79 characters. Two blank lines between top-level functions. Spaces around operators, no spaces around = in keyword arguments.
It's like the warehouse bin labeling system Diane instituted. Same format on every bin — item code, description, zone, max capacity. Before that, finding anything was a fifteen-minute exercise.
That's the analogy exactly. PEP 8 is the bin labeling standard for Python code. It doesn't make the bins hold more — it makes finding the right bin fast and reliable.
What about naming conventions? I've seen camelCase, snake_case, PascalCase — Python seems to have opinions.
Strong opinions. Functions and variables: snake_case — lowercase words, underscores between them. validate_product, error_messages, is_valid. Classes: PascalCase — each word capitalized, no separators. ProductValidator, OrderProcessor. Constants: UPPER_SNAKE_CASE. MAX_PRICE, DEFAULT_WAREHOUSE.
MAX_SKU_LENGTH = 20 # constant
class ProductValidator: # class
def validate_product(self, product: dict) -> list[str]: # method
error_messages = [] # variable
return error_messagesSo if I write def ValidateProduct(), every Python developer who sees it immediately knows I came from JavaScript or Java.
Or C#. The naming conventions are tribal markers. They signal that you've spent time in the community and understand the idioms. The next person on the codebase won't have to adjust their mental model when they hit your code.
Let me bring this back to validate_product. Four checks: name non-empty, SKU format, price positive, stock non-negative. Each check appends to errors or doesn't. Return errors at the end.
Clean structure. The SKU check is the interesting one — "starts with a letter and contains a hyphen." You could combine those into one condition or check them separately. Which is more readable?
Separate checks. If I combine with and, a caller gets one message covering two different problems. Separate checks give them "SKU must start with a letter" and "SKU must contain a hyphen" as distinct errors. Diane would rather know exactly which rule failed.
One error per violated rule. Granular feedback is more actionable than combined feedback. And the sentinel default for stock — use -1, not 0, because 0 is a valid stock level. A missing stock key needs to trigger an error, not silently pass.
If I used 0 as the default and stock was missing, the check if stock < 0 would pass silently and Diane would get a product with no stock key in her catalog. Using -1 means the error fires. That's the kind of detail that separates code that works from code that's correct.
Tomorrow: Week 4 begins. Maya's script is over three hundred lines now — routing logic tangled with reporting logic tangled with validation logic. It all works. It's also a maintenance problem. Next week is about how Python thinks about organizing code. Not one file, but many. Not one namespace, but separate modules with clear responsibilities.
So validate_product might eventually live in its own validation.py that other parts of the codebase import from?
Exactly. The function you write today is ready for that move. Clean signature, docstring, no global state, returns something useful. Well-styled code is portable code — you can pick it up and drop it into a module without rewriting anything around it.
Let's write it. Four checks, one error per rule, docstring first, snake_case throughout. Diane's going to use this on every product that comes through the intake form.
pass is a statement that does nothing — it is a syntactic placeholder required when Python's grammar demands a block body but you have no code to put there yet. pass appears in three contexts:
# Design scaffolding
def validate_product(product: dict) -> list[str]:
pass # implements nothing — SyntaxError without pass
# Intentional no-op
match order.get("status"):
case "cancelled":
pass # cancelled orders need no action
case "shipped":
update_tracking(order)
# Exception silencing
try:
qty = int(raw_input)
except ValueError:
pass # leave qty at default if conversion fails... (Ellipsis): In type stubs and abstract interfaces, ... is an idiomatic alternative to pass. Same semantics, different convention: pass in implementation code, ... in protocol/stub declarations.
PEP 8 — key rules:
| Rule | Correct | Incorrect |
|---|---|---|
| Indentation | 4 spaces | tabs or 2 spaces |
| Line length | ≤79 chars (99 by team agreement) | scrolling horizontally |
| Top-level spacing | 2 blank lines between functions | 0 or 1 blank lines |
| Operators | x = 1, a + b | x=1, a+b |
| Keyword args | f(key=value) | f(key = value) |
| Functions/variables | snake_case | camelCase |
| Classes | PascalCase | snakecase |
| Constants | UPPER_SNAKE_CASE | lowercase |
Pitfall 1: Forgetting pass entirely. An empty if, for, def, or class body causes a SyntaxError. The block must contain at least one statement.
Pitfall 2: Using pass instead of return or raise. pass does nothing — execution continues to the next statement. If you need the function to exit early or signal an error, you need return or raise, not pass.
Pitfall 3: Silencing exceptions broadly. except Exception: pass hides every error including unexpected ones. Always catch the most specific exception you expect, and only silence it when you have a safe default.
pass has a modern equivalent in async contexts: async def f(): pass — but async functions returning None are fine, so pass there is purely scaffolding.
Linters enforce PEP 8 automatically. ruff is the current standard — it replaces flake8, isort, and parts of pylint with a single fast tool. Running ruff check . in any Python project will flag PEP 8 violations and many common bugs. Most teams configure it as a pre-commit hook so style violations never reach code review.