Python Course — Day 8

Conventions, Patterns & Reading Code

Today we master the unwritten rules of Python — naming conventions, common patterns, scope, and how to read code hints in VS Code. These skills separate beginners from confident programmers.

Lab Modules

📌 Setup

VS Code → H:\Pooja → create day8 → terminal → cd H:\Pooja\day8

Naming Conventions

Variable names like x or data1 are legal but terrible. Professional programmers follow naming conventions — agreed-upon styles that make code readable.

💡 snake_case — Python's Convention

In snake_case, every word is lowercase and separated by underscores:
student_name, total_price, is_logged_in, calculate_tax

This is the standard convention in Python for variables, functions, and file names. You'll see it in almost every Python code you read. It's not a rule the computer enforces — StudentName would also work — but the community agreed on snake_case because it's easy to read.

💡 camelCase — Other Languages' Convention

In camelCase, the first word is lowercase and each following word starts with a capital letter:
studentName, totalPrice, isLoggedIn, calculateTax

This is used in JavaScript, Java, and C#. If you read code from those languages, you'll see camelCase everywhere. In Python, we don't use camelCase for variables and functions (except for class names, which use PascalCase — but that's for later).

💡 Why Does This Matter?

Readability. Compare: userscoretotal vs user_score_total. The second is instantly readable. When you work on a team (or read someone else's code), consistent naming means everyone can understand the code faster. It's like everyone agreeing to drive on the same side of the road — the convention itself matters less than everyone following it.
TASK 1.1Practice the Convention
PYTHONnaming.py
# GOOD — Python snake_case convention first_name = "Pooja" max_retry_count = 3 is_active = True def calculate_total(price, tax_rate): return price + (price * tax_rate) # BAD — inconsistent or unclear # firstName = "Pooja" # camelCase (not Python style) # MAXRETRYCOUNT = 3 # all caps, no separation # x = True # meaningless name # def calcTot(p, t): ... # abbreviated, unclear print(calculate_total(100, 0.18))
📝 Quiz

Which follows Python naming convention?

A totalPrice
B TotalPrice
C total_price
D TOTALPRICE
Python uses snake_case: all lowercase with underscores between words.

Special Characters in Strings

Sometimes you need to put things inside a string that you can't just type normally — like a new line, a tab, or a quote character. Python uses the backslash \ to create these escape characters.

💡 What Is an Escape Character?

The backslash \ tells Python: "the next character is special, don't treat it normally." It's called "escaping" because you're escaping the normal meaning.

\nnew line (like pressing Enter)
\ttab (large indent space)
\\literal backslash (prints a single \)
\"literal double quote inside a double-quoted string
\'literal single quote inside a single-quoted string
TASK 2.1Escape Characters in Action
PYTHONescape.py
# \n = new line (most common!) print("Line 1\nLine 2\nLine 3") print() # \t = tab (for alignment) print("Name\tAge\tCity") print("Pooja\t20\tMumbai") print("Alice\t22\tDelhi") print() # \\ = literal backslash (for Windows paths) print("File path: C:\\Users\\Pooja\\Documents") # \" = quote inside a string print("She said \"hello\" to me")
Output
Line 1 Line 2 Line 3 Name Age City Pooja 20 Mumbai Alice 22 Delhi File path: C:\Users\Pooja\Documents She said "hello" to me
🐛 Debug — Backslash in Path
PYTHONdebug_path.py
path = "C:\new_folder\test.txt" print(path)

Run this — the output looks wrong! Why?

The \n in \new_folder is interpreted as a newline, and \t in \test.txt as a tab. Fix: use double backslashes "C:\\new_folder\\test.txt" or a raw string r"C:\new_folder\test.txt" (the r prefix disables escape processing).
📝 Quiz

What does print("A\tB\tC") output?

A A\tB\tC
B A B C (with tab spaces)
C AtBtC
D Error
\t is the tab character — it creates a large space, typically aligning to the next tab stop.

Looping Patterns Deep Dive

You know the basics of for loops. Now let's understand every way to loop over lists and dictionaries, and why the syntax looks the way it does.

TASK 3.1Three Ways to Loop a List

In each example, notice: for and in are keywords (built into Python). The variable between them is a name you choose. Most developers use conventional names — but they could be anything.

PYTHONloop_list.py
fruits = ["apple", "banana", "cherry"] # Way 1: Loop by item (most common) # 'fruit' is YOUR variable name — could be 'item', 'f', 'x', anything # Convention: use singular of the list name (fruits → fruit) for fruit in fruits: print(fruit) print() # Way 2: Loop by index number # 'i' is YOUR variable name — convention for index. Could be 'index', 'n' # range(len(fruits)) generates [0, 1, 2] for i in range(len(fruits)): print("Index", i, "=", fruits[i]) print() # Way 3: Loop with both index AND item using enumerate() # 'index' and 'fruit' are BOTH your variable names for index, fruit in enumerate(fruits): print(index, "→", fruit)
TASK 3.2Three Ways to Loop a Dictionary
PYTHONloop_dict.py
person = {"name": "Pooja", "age": 20, "city": "Mumbai"} # Way 1: Loop over keys (default behavior) # 'key' is YOUR variable name — convention. Could be 'k', 'field', etc. for key in person: print(key, "=", person[key]) print() # Way 2: Loop over key-value pairs using .items() # .items() returns something like: [("name","Pooja"), ("age",20), ("city","Mumbai")] # Each item is a TUPLE (pair). Python "unpacks" it into two variables. # 'key' and 'value' are YOUR variable names — convention uses 'k, v' or 'key, value' for key, value in person.items(): print(key, "=", value) print() # Way 3: Loop over values only for value in person.values(): print(value)

💡 Tuple Unpacking — Why for key, value in ... Works

When you write for key, value in person.items():, here's what's happening step by step:

person.items() returns pairs: [("name", "Pooja"), ("age", 20), ("city", "Mumbai")]
Each pair is a tuple — like a mini list with 2 items.
Python can unpack a tuple into separate variables: key, value = ("name", "Pooja") puts "name" into key and "Pooja" into value.

This unpacking happens automatically each iteration of the loop. It's the same as writing:
pair = ("name", "Pooja")
key = pair[0]   value = pair[1]
...but much shorter and cleaner.
TASK 3.3See Tuple Unpacking Yourself
PYTHONunpack.py
# Manual unpacking pair = ("name", "Pooja") k, v = pair # unpacks into two variables print(k) # "name" print(v) # "Pooja" # This is exactly what happens inside: for key, value in dict.items() # Each iteration, one tuple is unpacked into key and value.
✏️ Exercise — Complete the Loop

Complete this to print every student's name and grade:

PYTHONexercise
grades = {"Alice": "A", "Bob": "B", "Charlie": "C"} for ???, ??? in grades.items(): print(name, "got", grade)
name and grade — These are variable names you choose. They must match what's used in the print line below. .items() gives tuples like ("Alice", "A"), which unpacks into name="Alice" and grade="A".

Default Arguments in Functions

Sometimes a function parameter has a value that's used most of the time. Instead of forcing the caller to provide it every time, you can set a default value.

🏠 Real-World Analogy

At a coffee shop: "One coffee, please." The barista assumes regular size and no sugar unless you say otherwise. Those are defaults. But you CAN override them: "Large, two sugars." Default arguments work the same way — sensible assumptions you can override when needed.

TASK 4.1Default Arguments in Action
PYTHONdefaults.py
# 'greeting' has a default value of "Hello" # 'greeting' is a PARAMETER NAME (your choice), "Hello" is the DEFAULT VALUE def welcome(name, greeting="Hello"): print(greeting + ", " + name + "!") # Call without providing greeting — uses default "Hello" welcome("Pooja") # Hello, Pooja! # Call WITH a greeting — overrides the default welcome("Pooja", "Good morning") # Good morning, Pooja! print() # Practical example: power function # Most of the time, we want power of 2 (squaring). So make 2 the default. def power(base, exponent=2): return base ** exponent print(power(5)) # 25 (5 squared — used default exponent=2) print(power(5, 3)) # 125 (5 cubed — overrode with exponent=3)
⚠️ Rule: Defaults Must Come Last

Parameters with defaults must come after parameters without defaults. def func(a, b=10) is valid. def func(a=10, b) is a SyntaxError. Think of it like this: the required ingredients come first, the optional ones at the end.

🐛 Debug — Default Before Required
PYTHONdebug_default.py
def greet(greeting="Hi", name): print(greeting, name) greet("Pooja")
SyntaxError: parameter with default (greeting) comes before parameter without default (name). Fix: swap them: def greet(name, greeting="Hi"):

Methods — Actions on Data

You've used methods like .append(), .upper(), and .items(). Let's understand what methods really are — without needing to know about classes or objects yet.

💡 Functions vs Methods — The Simple Difference

A function stands on its own: len(my_list), print("hi"), type(42)
A method is attached to a value with a dot: my_list.append(5), "hello".upper()

Think of it this way: a function is like a tool anyone can use (like a hammer). A method is like a button on a specific device (like the shutter button on a camera). The button only exists on the camera — it's attached to it. Similarly, .append() only exists on lists, .upper() only exists on strings.

Different types have different methods. That's why "hello".append(5) fails — strings don't have .append(). And [1,2,3].upper() fails — lists don't have .upper().
TASK 5.1Methods by Type — Cheat Sheet
PYTHONmethods_cheat.py
# === STRING METHODS === text = " Hello, World! " print(text.strip()) # "Hello, World!" — remove whitespace print(text.lower()) # " hello, world! " print(text.upper()) # " HELLO, WORLD! " print(text.replace("World", "Python")) # swap words print("a,b,c".split(",")) # ["a", "b", "c"] — split into list print("-".join(["a", "b", "c"])) # "a-b-c" — join list into string print() # === LIST METHODS === nums = [3, 1, 4] nums.append(9) # add to end: [3, 1, 4, 9] nums.sort() # sort in place: [1, 3, 4, 9] nums.reverse() # reverse in place: [9, 4, 3, 1] nums.remove(4) # remove first 4: [9, 3, 1] print(nums) print(nums.index(3)) # 1 — position of value 3 print() # === DICT METHODS === d = {"a": 1, "b": 2} print(list(d.keys())) # ["a", "b"] print(list(d.values())) # [1, 2] print(list(d.items())) # [("a", 1), ("b", 2)] print(d.get("c", 0)) # 0 — get value or default (no KeyError!)
📝 Quiz

Why does (5).append(3) fail?

A 5 is too small
B You need to use add() instead
C append() only works on strings
D .append() is a list method — integers don't have it
Different types have different methods. .append() belongs to lists. Integers have no .append() method, so you get an AttributeError.

Scope — Global vs Local

Understanding scope means understanding where a variable exists and who can see it. This is a common source of confusion, so let's make it crystal clear.

💡 The Three Rules of Scope

Rule 1: A variable created outside any function is global — it can be read from anywhere, including inside functions.

Rule 2: A variable created inside a function is local — it only exists while the function runs and cannot be seen outside.

Rule 3: If you assign to a variable inside a function, Python always creates a new local variable — even if a global variable with the same name already exists. The global one is untouched.
TASK 6.1Rule 3 Is Tricky — See It
PYTHONscope_tricky.py
color = "blue" # GLOBAL variable def paint(): color = "red" # This creates a NEW LOCAL variable called 'color'! # It does NOT change the global 'color'. print("Inside function:", color) # red (local) paint() print("Outside function:", color) # blue (global — unchanged!)
Output
Inside function: red Outside function: blue

Inside the function, color is "red" (local copy). Outside, color is still "blue" (the global was never touched). They're two completely different variables that happen to share a name.

TASK 6.2Reading Global Without Modifying — Works Fine
PYTHONscope_read.py
PI = 3.14159 # GLOBAL constant def circle_area(radius): # Reading PI is fine — no assignment, so Python uses the global return PI * radius ** 2 print(circle_area(5)) # 78.53975 — works! PI was read, not modified.
TASK 6.3The global Keyword — How (and Why Not)
PYTHONscope_global.py
count = 0 def increment(): global count # "Don't create a local — use the GLOBAL count" count = count + 1 increment() increment() print(count) # 2 — the global WAS modified # THIS WORKS but is considered BAD PRACTICE. # Better approach: use parameters and return def increment_better(value): return value + 1 count = 0 count = increment_better(count) count = increment_better(count) print(count) # 2 — same result, clearer code
📝 Quiz

x = 10
def change():
    x = 99
change()
print(x)

What is printed?

A 99
B 10
C Error
D None
x = 99 inside the function creates a new local variable. It does NOT change the global x. So the global x is still 10.

Type Annotations & Reading VS Code Hints

When you hover over a function in VS Code, a tooltip appears showing its signature — what inputs it takes, their types, and what it returns. Learning to read these hints makes you much faster at using any function.

💡 What Are Type Annotations?

Type annotations are optional hints you add to your code to say: "this variable should be an int" or "this function returns a string." Python does NOT enforce them — your code runs the same with or without them. They exist purely to help programmers (and VS Code) understand the code better. They're especially useful in large projects where many people work on the same code.
TASK 7.1Basic Type Annotations
PYTHONannotations.py
# Variable annotations name: str = "Pooja" # name should be a string age: int = 20 # age should be an integer height: float = 5.4 # height should be a float is_active: bool = True # is_active should be a boolean # Function annotations — annotate parameters AND return type def greet(name: str, times: int = 1) -> str: return ("Hello, " + name + "! ") * times # Reading the signature: # greet(name: str, times: int = 1) -> str # ^^^^^^^^ name must be a string # ^^^^^^^^^^^ times must be an int, defaults to 1 # ^^^^^^ returns a string print(greet("Pooja")) # Hello, Pooja! print(greet("Pooja", 3)) # Hello, Pooja! Hello, Pooja! Hello, Pooja!

💡 How to Read a VS Code Hover Tooltip

When you hover over a function in VS Code, you'll see something like this:
(method) str.replace(old: str, new: str, count: int = -1) -> str

📖 Reading the Tooltip — Piece by Piece

(method) — this is a method (attached to a value with a dot)
str.replace — it belongs to strings, named replace
old: str — first input: the text to find. Must be a str.
new: str — second input: the replacement text. Must be a str.
count: int = -1 — third input: how many replacements. int, default is -1 (meaning replace all). This is optional because it has a default.
-> str — it returns a string (the new text with replacements made)
TASK 7.2Practice Reading Tooltips

In VS Code, type this code. Then hover your mouse over each function/method name and read the tooltip. Match what you see with what the code does.

PYTHONread_hints.py
# Hover over 'len' — you'll see: len(obj: Sized) -> int # Meaning: takes something with a size, returns an int print(len("hello")) # 5 # Hover over 'split' — you'll see: str.split(sep: str | None = None, ...) -> list[str] # Meaning: optional separator (str or None), returns a list of strings print("a-b-c".split("-")) # ["a", "b", "c"] # Hover over 'append' — you'll see: list.append(object) -> None # Meaning: takes any object, returns None (modifies the list in-place) nums = [1, 2] nums.append(3) print(nums)

💡 The | Symbol — "or" in Types

When you see str | None in a tooltip, the | means "or". So str | None means "this can be a string or None." You'll also see things like int | float meaning "this can be an integer or a float."

And if you see a type you don't recognize (like Iterable or Callable), that's a custom type created by programmers. Don't worry about it — just know it's a more specific description of what the function expects.
TASK 7.3Write Your Own Annotated Function

Write a function that takes a list of numbers and returns the average. Use type annotations.

def average(numbers: list) -> float:
    total = sum(numbers)
    return total / len(numbers)

print(average([10, 20, 30])) # 20.0
📝 Quiz

You see this tooltip: round(number: float, ndigits: int = 0) -> int
Which parameter is optional?

A number
B Both are required
C ndigits (it has a default = 0)
D The return type
ndigits: int = 0 has a default value of 0, making it optional. number: float has no default, so it's required.
📝 Quiz

What does str | None mean in a type annotation?

A The value can be either a string or None
B The value must be both a string and None
C Divide the string by None
D The string cannot be None
The | symbol means "or" in type annotations. str | None means the value can be a string OR the special value None.

Completion Checklist

🎯 I Can Now…

🏆

You Can Read Code Like a Pro!

Naming conventions, escape characters, looping patterns, scope rules, and type annotations — these are the skills that let you read ANY Python code and understand it. You're no longer just writing code; you're reading and understanding code written by others. That's a massive level-up.