Functions#

A function is a reusable block of code defined with the def keyword. Calling it executes the code inside, achieving the “write once, use many times” effect. Python functions support default parameters, keyword arguments, variadic parameters, and can be passed around and returned as first-class objects.


Defining and Calling Functions#

1
2
3
4
5
6
7
# Define a function
def greet(name):
    print(f"Hello, {name}!")

# Call the function
greet("Alice")   # Hello, Alice!
greet("Bob")     # Hello, Bob!

Return Values#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def add(a, b):
    return a + b

result = add(3, 5)
print(result)  # 8

# Can return multiple values (actually returns a tuple)
def min_max(numbers):
    return min(numbers), max(numbers)

lo, hi = min_max([3, 1, 4, 1, 5, 9])
print(lo, hi)  # 1 9

# Without return, or return with no value, returns None
def do_nothing():
    pass

result = do_nothing()
print(result)  # None

Parameter Types#

Positional Arguments#

1
2
3
4
def describe(name, age, city):
    print(f"{name}, age {age}, lives in {city}")

describe("Alice", 25, "Taipei")  # passed in order

Default Arguments#

1
2
3
4
5
6
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")           # Hello, Alice!
greet("Bob", "Hi")       # Hi, Bob!
greet("Charlie", greeting="Hey")  # Hey, Charlie!

Note: Default parameters must come after non-default parameters.

Keyword Arguments#

1
2
3
4
5
def describe(name, age, city):
    print(f"{name}, age {age}, lives in {city}")

# Pass by keyword — order does not matter
describe(age=25, city="Taipei", name="Alice")

*args: Accept any number of positional arguments#

1
2
3
4
5
6
def total(*args):
    print(args)   # received as a tuple
    return sum(args)

print(total(1, 2, 3))        # 6
print(total(1, 2, 3, 4, 5))  # 15

**kwargs: Accept any number of keyword arguments#

1
2
3
4
5
6
7
8
9
def print_info(**kwargs):
    print(kwargs)  # received as a dict
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25, city="Taipei")
# name: Alice
# age: 25
# city: Taipei

Combining all parameter types#

1
2
3
4
5
6
7
def flexible(required, *args, default="hello", **kwargs):
    print(f"Required parameter: {required}")
    print(f"Extra positional args: {args}")
    print(f"Default parameter: {default}")
    print(f"Extra keyword args: {kwargs}")

flexible("mandatory", 1, 2, 3, default="world", x=10, y=20)

Variable Scope#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
x = 10  # global variable

def foo():
    x = 20  # local variable, does not affect the global x
    print(x)  # 20

foo()
print(x)  # 10

# To modify a global variable inside a function, use global
def bar():
    global x
    x = 30

bar()
print(x)  # 30

Lambda Functions (Anonymous Functions)#

A lambda is a concise single-line function, commonly used with sorted(), map(), and filter():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Syntax: lambda parameters: expression
square = lambda x: x ** 2
print(square(5))  # 25

add = lambda a, b: a + b
print(add(3, 4))  # 7

# Use with sorted()
words = ["banana", "apple", "cherry", "kiwi"]
sorted_words = sorted(words, key=lambda w: len(w))
print(sorted_words)  # ['kiwi', 'apple', 'banana', 'cherry']

# Sort a list of dicts with sorted()
students = [
    {"name": "Alice", "score": 85},
    {"name": "Bob", "score": 92},
    {"name": "Charlie", "score": 78},
]
by_score = sorted(students, key=lambda s: s["score"], reverse=True)
for s in by_score:
    print(f"{s['name']}: {s['score']}")

Common Higher-Order Functions#

A higher-order function is one that accepts another function as an argument. Lambda expressions are most commonly used as arguments to these functions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# map(): apply a function to every element
squares = list(map(lambda x: x ** 2, numbers))
print(squares)   # [1, 4, 9, 16, 25]

# filter(): keep elements that satisfy a condition
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)     # [2, 4, 6, 8, 10]

# sorted(): customise the sort key
words = ["banana", "apple", "cherry", "kiwi"]
by_length = sorted(words, key=lambda w: len(w))
print(by_length) # ['kiwi', 'apple', 'banana', 'cherry']

For the full coverage of map(), filter(), and sorted() — including multiple sequences, multi-key sorting, and advanced patterns — see CH11: Common Built-in Functions.


Closures#

A function can remember variables from its enclosing scope:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def make_multiplier(factor):
    def multiplier(x):
        return x * factor  # remembers factor
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))  # 10
print(triple(5))  # 15

Recursion#

A function can call itself:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)

print(factorial(5))  # 120 (5 × 4 × 3 × 2 × 1)

# Fibonacci sequence
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

for i in range(10):
    print(fibonacci(i), end=" ")
# 0 1 1 2 3 5 8 13 21 34

Practical Examples#

Moving average#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def moving_average(prices, window):
    """Calculate a moving average."""
    result = []
    for i in range(len(prices) - window + 1):
        avg = sum(prices[i:i + window]) / window
        result.append(round(avg, 2))
    return result

prices = [100, 102, 98, 105, 110, 108, 112]
ma3 = moving_average(prices, 3)
print(ma3)  # [100.0, 101.67, 104.33, 107.67, 110.0]

Quicksort#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def quicksort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quicksort(left) + middle + quicksort(right)

data = [3, 6, 8, 10, 1, 2, 1]
print(quicksort(data))  # [1, 1, 2, 3, 6, 8, 10]