Lab 07: Functions & Scope

🎯 Objective

Define reusable functions, understand Python's scoping rules (LEGB), work with default arguments, *args, **kwargs, and return multiple values.

📚 Background

Functions are the fundamental unit of code reuse in Python. They encapsulate logic, reduce duplication, and make code testable. Python functions are first-class objects — they can be stored in variables, passed as arguments, and returned from other functions. Understanding scope (where a variable lives) prevents subtle bugs.

⏱️ Estimated Time

35 minutes

📋 Prerequisites

  • Lab 6: Control Flow

🛠️ Tools Used

  • Python 3.12

🔬 Lab Instructions

Step 1: Defining and Calling Functions

📸 Verified Output:

💡 The docstring (triple-quoted string after def) documents what the function does. Python's help() and IDE tools read docstrings. Always write them — your future self will thank you.

Step 2: Default Arguments and Keyword Arguments

📸 Verified Output:

💡 Important: Never use mutable defaults like def f(lst=[]). The list is created ONCE and shared across all calls. Use def f(lst=None): lst = lst or [] instead.

Step 3: Returning Multiple Values

📸 Verified Output:

Step 4: *args and **kwargs

📸 Verified Output:

Step 5: Scope — LEGB Rule

Python looks up variables in this order: Local → Enclosing → Global → Built-in.

📸 Verified Output:

💡 Using global is usually a code smell — prefer returning values instead. But understanding scope is essential for debugging "variable not defined" errors.

Step 6: Lambda Functions

📸 Verified Output:

Step 7: Recursive Functions

📸 Verified Output:

Step 8: Functions as First-Class Objects

📸 Verified Output:

✅ Verification

Expected output:

🚨 Common Mistakes

  1. Mutable default arguments: def f(lst=[]) — the list persists across calls. Use def f(lst=None): lst = lst or [].

  2. Forgetting return: A function with no return returns None — leads to NoneType has no attribute errors.

  3. Modifying a global without global keyword: Python creates a new local variable instead.

  4. Infinite recursion: Every recursive function needs a base case that stops the chain.

  5. Overusing lambda: Complex lambdas hurt readability — use a named def instead.

📝 Summary

  • Functions: def name(params): ... return value — docstrings are essential

  • Default args: def f(x, y=10) — never use mutable defaults

  • Multiple returns: return a, b, c packs into a tuple; unpack with a, b, c = f()

  • *args collects extra positional args as a tuple; **kwargs as a dict

  • LEGB scope: Local → Enclosing → Global → Built-in

  • Lambda: one-expression anonymous function — use for simple key functions

  • Functions are first-class: pass them, store them, return them

🔗 Further Reading

Last updated