IT243 Programming with Python

Programming with PythonUnit 49 min read

Functions, Modules, Recursion & Scope in Python

Unit 4 of Programming with Python covers how to define, call, and reuse functions; organize code into modules; handle scope rules; and implement recursion. Learn parameter passing, lambda functions, and Python’s import system with real-world examples from Nepalese apps like eSewa and Daraz.

TAKEAWAYS

  • Functions let you group reusable code with parameters and return values, reducing redundancy.
  • Scope rules (LEGB) determine where variables are accessible: Local → Enclosing → Global → Built-in.
  • Modules help organize code into files (.py) and avoid naming conflicts via import statements.
  • Recursion solves problems by breaking them into smaller subproblems (e.g., factorial, Fibonacci).
  • Lambda functions create anonymous functions for short, one-time operations (e.g., sorting keys).
  • Parameter passing in Python is pass-by-object-reference: mutable objects (lists) can be modified inside functions.

1. Functions: Definition, Calling, and Scope

10213243
Mutable list after `modify_list()` (1.4): original list modified in-place
global x = 10enclosing x = 20local x = 30Local (inner)
LEGB scope resolution in nested functions (example from 1.3)

1.1 What is a Function?

A function is a reusable block of code that performs a specific task. It:

  • Takes inputs (parameters/arguments).
  • Executes statements.
  • Returns an output (optional).
def greet(name):  # 'name' is a parameter
    return f"Hello, {name}!"

print(greet("Rohan"))  # 'Rohan' is an argument

Output:

Hello, Rohan!

1.2 Types of Functions

Type Example Key Feature
Built-in len(), print(), max() Predefined in Python
User-defined def add(a, b): return a + b Created by the programmer
Lambda (Anonymous) lambda x: x**2 Short, one-line functions

1.3 Scope Rules (LEGB)

Variables are accessed in this order:

  1. Local (inside the function)
  2. Enclosing (in nested functions)
  3. Global (module-level)
  4. Built-in (Python’s default, e.g., print)
x = 10  # Global

def outer():
    x = 20  # Enclosing
    def inner():
        x = 30  # Local
        print(x)  # 30 (Local)
    inner()
    print(x)  # 20 (Enclosing)

outer()
print(x)  # 10 (Global)

Output:

30
20
10

1.4 Parameter Passing

Python uses pass-by-object-reference:

  • Immutable objects (e.g., int, str, tuple) → copied (changes inside function don’t affect original).
  • Mutable objects (e.g., list, dict) → reference passed (changes persist).
def modify_list(lst):
    lst.append(4)  # Modifies the original list

my_list = [1, 2, 3]
modify_list(my_list)
print(my_list)  # Output: [1, 2, 3, 4]

2. Modules: Organizing Code

2.1 Why Use Modules?

  • Reusability: Share code across programs.
  • Avoid naming conflicts: Use import module_name.
  • Modularity: Split large programs into manageable files.

2.2 Importing Modules

# Import entire module
import math
print(math.sqrt(16))  # 4.0

# Import specific functions
from math import pi, sin
print(sin(pi/2))  # 1.0

# Import with alias
import numpy as np
print(np.array([1, 2, 3]))

2.3 Creating Your Own Module

  1. Save code in a file (e.g., mymath.py):
    def add(a, b):
        return a + b
    
  2. Import it:
    import mymath
    print(mymath.add(5, 3))  # 8
    

2.4 __name__ and if __name__ == "__main__":

  • __name__ is "__main__" when the script runs directly.
  • Useful for conditional execution (e.g., run tests only when the file is executed, not imported).
def hello():
    print("Hello!")

if __name__ == "__main__":
    hello()  # Runs only when executed directly

3. Recursion

flowchart TD
    A["factorial(4)"] --> B["4 × factorial(3)"]
    B --> C["factorial(3)"] --> D["3 × factorial(2)"]
    D --> E["factorial(2)"] --> F["2 × factorial(1)"]
    F --> G["factorial(1)"] --> H["1 × factorial(0)"]
    H --> I["factorial(0)"] --> J["1 (base case)"]

Call stack for factorial(4) (3.2)

3.1 What is Recursion?

A function calls itself to solve smaller instances of the same problem. Base case: Stops recursion (prevents infinite loops). Recursive case: Breaks the problem into smaller subproblems.

3.2 Example: Factorial

def factorial(n):
    if n == 0:  # Base case
        return 1
    else:
        return n * factorial(n - 1)  # Recursive case

print(factorial(4))  # 24

Trace:

Call Stack n Return Value
factorial(4) 4 4 × factorial(3)
factorial(3) 3 3 × factorial(2)
factorial(2) 2 2 × factorial(1)
factorial(1) 1 1 × factorial(0)
factorial(0) 0 1 (base case)

Output: 4 × 3 × 2 × 1 × 1 = 24

3.3 Real-World Example: Fibonacci Sequence

Problem: Calculate the 6th Fibonacci number (0, 1, 1, 2, 3, 5, 8, ...). Solution:

def fib(n):
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

print(fib(6))  # 8

Trace:

fib(6) → fib(5) + fib(4)
fib(5) → fib(4) + fib(3)
fib(4) → fib(3) + fib(2)
fib(3) → fib(2) + fib(1)
fib(2) → fib(1) + fib(0) → 1 + 0 = 1
fib(1) → 1
fib(0) → 0

Output: 8


4. Lambda Functions

Rohan,200Sita,221Ram,192
Students array before sorting (4.2)

4.1 Syntax

lambda arguments: expression
  • No return statement (expression is returned automatically).
  • Used for short, one-time functions (e.g., sorting).

4.2 Example: Sorting with Lambda

students = [("Rohan", 20), ("Sita", 22), ("Ram", 19)]
# Sort by age (second element in tuple)
sorted_students = sorted(students, key=lambda x: x[1])
print(sorted_students)

Output:

[('Ram', 19), ('Rohan', 20), ('Sita', 22)]

4.3 Real-World Example: eSewa Transaction Fees

eSewa calculates fees dynamically. A lambda function could compute a 1% fee for transactions:

fee = lambda amount: amount * 0.01
print(fee(1000))  # 10.0

5. Exam Tip

  1. Function Definition vs. Call:

    • def func(a, b): → definition.
    • func(1, 2) → call.
    • Common mistake: Forgetting return or using = instead of == in conditions.
  2. Scope Pitfalls:

    • Global variables inside functions must be declared with global.
    • Local variables shadow globals if names clash.
  3. Recursion Questions:

    • Always identify the base case and recursive case.
    • Draw the call stack (like the factorial trace above).
  4. Modules:

    • Know how to import (import, from ... import, as).
    • Understand __name__ for conditional execution.
  5. Lambda Tricks:

    • Useful in sorted(), map(), filter().
    • Example: list(map(lambda x: x*2, [1, 2, 3])) → [2, 4, 6].

In the Real World

  1. eSewa (Nepal):

    • Uses modules to separate payment logic, user authentication, and transaction history.
    • Lambda functions might calculate dynamic fees (e.g., fee = lambda amount: amount * (1 + tax_rate)).
  2. Daraz Order Processing:

    • Queues (FIFO) manage orders, but functions handle each order’s status (e.g., update_order_status(order_id, "shipped")).
    • Recursion could traverse product categories in a nested structure (e.g., electronics → mobile → Samsung).
  3. Ncell Billing System:

    • Modules separate customer data, billing calculations, and report generation.
    • Lambda functions compute discounts (e.g., discount = lambda plan: plan.discount_rate * plan.amount).
  4. Bank Loan Interest (Nepalese Banks):

    • Recursive functions calculate compound interest:
      def compound_interest(principal, rate, years):
          if years == 0:
              return principal
          else:
              return (principal * (1 + rate)) * compound_interest(principal, rate, years - 1)
      
    • Modules organize loan types (home, car, personal) in separate files.

flowchart TD
    A["Function Call\n`greet('Rohan')`"] --> B["Local Scope\n`name = 'Rohan'`"]
    A --> C["Global Scope\n`x = 10`"]
    B --> D["Return\n'Hello, Rohan!'"]
    C --> E["Unchanged\n`x` still 10"]
flowchart TD
    A["Recursive Function\n`fib(6)`"] --> B["fib(5) + fib(4)"]
    B --> C["fib(4) + fib(3)"]
    C --> D["fib(3) + fib(2)"]
    D --> E["fib(2) + fib(1)"]
    E --> F["fib(1) + fib(0)"]
    F --> G["Base Case\nfib(0) = 0"]
    F --> H["Base Case\nfib(1) = 1"]
    G --> I["Returns 1"]
    H --> I

In the real world

  • eSewa Transaction Processing: Uses recursion in backend systems to validate nested transaction hierarchies (e.g., splitting bulk payments into individual merchant accounts).
  • Daraz Order Fulfillment: Employs modules to separate inventory management (e.g., inventory.py), order processing (e.g., orders.py), and shipping logic (e.g., shipping.py), avoiding naming conflicts via imports.
  • Ncell Billing System: Applies lambda functions to dynamically calculate variable taxes/fees (e.g., fee = lambda amount, tax_rate: amount * (1 + tax_rate)) for different user tiers.

Based on the TU BIM syllabus for Programming with Python (IT243), unit 4.

Discussion

Loading…