Python B02 · Lesson 5 of 5

Functions

User-defined · Arguments · Scope (LEGB) · Lambda · Recursion

A function is a block of code that performs a specific task and can be reused anywhere in your program. Instead of writing the same code 10 times, you write it once in a function and call it 10 times.

1. Why Use Functions?

From your file (functions.py), four key reasons:

ReasonMeaning
Code ReusabilityWrite once, use many times
ModularityBreak big programs into smaller, manageable pieces
MaintainabilityFix a bug in one place, it's fixed everywhere
AbstractionHide complex logic behind a simple function name

2. Types of Functions

Python has three types:

3. Defining & Calling Functions

def function_name(parameters):
    # code block
    return value

From your file — two examples:

# Function with no parameters
def add_two_numbers():
    a = 10
    b = 10
    sum = a + b
    return sum

# Function with parameters + docstring
def sum_of_nums(a, b):
    '''prints the sum of two numbers'''   # Docstring
    sum = a + b
    print(sum)

# Calling the functions:
print(add_two_numbers())     # 20
sum_of_nums(10, 20)          # 30
return vs no return
If a function has no return statement, it automatically returns None. Always check whether you need the result back or just the side effect (like printing).
Docstring
A docstring is a string placed at the start of a function to explain what it does. Use help(function_name) to see it. From file: help(print) shows print's built-in docstring.

4. Types of Arguments

1. Positional Arguments

Arguments matched by position — first arg → first param, second → second, etc.

def subtract(a, b):
    print(a - b)

subtract(10, 3)   # a=10, b=3 → prints 7

2. Keyword Arguments

Arguments passed using parameter names — position doesn't matter.

def greet(name, message):
    print(message, name)

greet(message="Hello", name="Alice")   # Hello Alice

3. Default Arguments

Parameters with a default value. If no argument is passed for that parameter, the default is used.

def greet(name, message="Hello"):   # message has default
    print(message, name)

greet("Alice")             # Hello Alice (uses default)
greet("Bob", "Hi")         # Hi Bob (overrides default)
Order matters
Default parameters must come after non-default parameters. def f(a=10, b) is a SyntaxError. From file: def sum(b, a=10) — default a comes after non-default b.

4. Variable-length Arguments (*args)

Use when the number of arguments is not known in advance. Collects all positional arguments into a tuple.

def total(*args):
    result = 0
    for num in args:
        result += num
    return result

print(total(1, 2, 3, 5))    # 11

5. Keyword Variable-length Arguments (**kwargs)

Use when arguments come with names and the count is unknown. Collects into a dictionary.

def display(**kwargs):
    for key, value in kwargs.items():
        print(key, "=", value)

display(name="Alice", age=20)
# name = Alice
# age = 20

5. Variable Scope & the LEGB Rule

A variable's scope determines where it can be accessed. Python searches for a variable name in this order: L → E → G → B

LetterScopeWhere
LLocalInside the current function
EEnclosingInside outer (enclosing) function
GGlobalTop-level module code
BBuilt-inPython's built-in names (print, len, etc.)
a = 20   # Global

def demo():
    a = 10   # Enclosing (for inner functions)
    def sample():
        nonlocal a
        a = 5   # modifies enclosing a
        print("inner", a)   # 5
    sample()
    print("enclosing", a)   # 5 (changed by nonlocal)

demo()
print("outer", a)   # 20 (global unchanged)
nonlocal
Inside a nested function, use nonlocal to modify the enclosing function's variable. Without it, assignment creates a new local variable instead.

6. Lambda Functions

A lambda is a single-line anonymous function. Defined using the lambda keyword. Used for short, throwaway operations.

lambda arguments : expression

From your file:

# Equivalent functions:
square_lambda = lambda x : x * x

def square(x):
    return x * x

print(square_lambda(5))   # 25

# Lambda used inline (from    file):
number = int(input("Enter the number: "))
print("square =", (lambda x : x * x)(number))

# Two arguments:
add = lambda x, y : x + y
print(add(3, 4))   # 7

7. Recursion

Recursion is a technique where a function calls itself. Every recursive function must have two parts:

Classic example from your file — factorial:

# n! = n × (n-1)!
# 5! = 5 × 4! = 5 × 4 × 3! = ... = 120

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

print(factorial(5))   # 120

Trace of factorial(5) — how the calls unfold (from trace table):

CallnumberReturns
factorial(5)55 × factorial(4)
factorial(4)44 × factorial(3)
factorial(3)33 × factorial(2)
factorial(2)22 × factorial(1)
factorial(1)11 (base case!)

Unwinding: 1 → 2×1=2 → 3×2=6 → 4×6=24 → 5×24=120

🛠 Drills — Practice Time

Drill 1 Build a Calculator Function
Write a function calculator(a, b, operation) that takes two numbers and an operation string ("add", "sub", "mul", "div") and returns the result.
def calculator(a, b, operation="add"):
    if operation == "add":
        return a + b
    elif operation == "sub":
        return a - b
    elif operation == "mul":
        return a * b
    elif operation == "div":
        return a / b

print(calculator(10, 5, "mul"))   # 50
print(calculator(10, 3))          # 13 (default add)
Drill 2 Sum with *args
Write a function total(*nums) that accepts any number of integers and returns their sum. Test it with 3 different calls.
def total(*nums):
    result = 0
    for n in nums:
        result += n
    return result

print(total(1, 2, 3))          # 6
print(total(10, 20, 30, 40))   # 100
print(total(5))                # 5
Drill 3 Lambda Conversions
Write the following as lambda functions:
(1) Convert Celsius to Fahrenheit: F = C × 9/5 + 32
(2) Check if a number is even (returns True/False)
(3) Find the larger of two numbers
# 1. Celsius to Fahrenheit
to_fahrenheit = lambda c : c * 9/5 + 32
print(to_fahrenheit(100))   # 212.0

# 2. Check even
is_even = lambda n : n % 2 == 0
print(is_even(4))    # True
print(is_even(7))    # False

# 3. Larger of two
bigger = lambda a, b : a if a > b else b
print(bigger(5, 9))  # 9
Drill 4 Recursive Power
Write a recursive function power(base, exp) that calculates base ** exp without using the ** operator.
Hint: base^n = base × base^(n-1), base case: exp == 0 returns 1.
def power(base, exp):
    if exp == 0:          # Base case: anything^0 = 1
        return 1
    else:                   # Recursive case
        return base * power(base, exp - 1)

print(power(2, 3))   # 8  (2×2×2)
print(power(5, 0))   # 1
print(power(3, 4))   # 81
Drill 5 Scope Predictor
Predict the output of this teacher code before revealing:
a = 20
def demo():
    a = 10
    def sample():
        nonlocal a
        a = 5
        print("inner", a)
    sample()
    print("enclosing", a)

demo()
print("outer", a)
# inner 5         — sample() sets nonlocal a to 5
# enclosing 5     — nonlocal changed demo()'s a from 10 to 5
# outer 20        — global a was never touched

📝 Quiz — 8 Questions

Score: 0 / 8
1 Which keyword is used to define a function in Python?
2 What does a function return if there is no return statement?
3 Which type of argument uses parameter names so order doesn't matter?
4 What does *args collect arguments into?
5 What does LEGB stand for?
6 What is a lambda function?
7 What happens if a recursive function has no base case?
8 What does the nonlocal keyword do?
← Lesson 4: Loops ↩ Back to Lesson 1