Chapter 4 — Functions, Modules, and Scope

Functions let you encapsulate and reuse logic. This chapter covers defining and calling functions, arguments, scope, modules, lambdas, and a brief introduction to recursion — the organizational backbone of larger data-science programs.

Learning Objectives

Prerequisites / Imports

Uses the standard statistics and math modules.

In [1]:
import statistics
import math

1 Defining and Calling Functions

A function packages a block of code under a name. Use def; return a value with return.

In [1]:
def greet(name):
    return f'Hello, {name}!'

print(greet('Data Scientist'))
Hello, Data Scientist!

2 Return Values and Multiple Returns

A function may return nothing (returns None) or several values as a tuple.

In [1]:
def min_max(values):
    return min(values), max(values)

lo, hi = min_max([3, 1, 4, 1, 5, 9])
print('min:', lo, 'max:', hi)
min: 1 max: 9

3 Argument Flavors

Positional, keyword, defaults, and variadic *args/**kwargs.

In [1]:
def describe(name, role='analyst', *skills, **meta):
    print(f'{name} - {role}')
    print('skills:', skills)
    print('meta:', meta)

describe('Ada', 'engineer', 'python','sql', team='data', level=3)
Ada - engineer
skills: ('python', 'sql')
meta: {'team': 'data', 'level': 3}

4 Mutable vs Immutable Arguments

Python passes object references. Mutating a mutable argument inside a function affects the caller; rebinding the name does not.

In [1]:
def add_item(lst, item):
    lst.append(item)   # mutates the shared list

nums = [1, 2, 3]
add_item(nums, 4)
print(nums)
[1, 2, 3, 4]
In [1]:
def rebind(lst):
    lst = [99]          # rebinds local name only

nums = [1, 2, 3]
rebind(nums)
print(nums)
[1, 2, 3]

5 Scope: Local, Global, nonlocal

Names follow the LEGB rule (Local, Enclosing, Global, Built-in). Use global/nonlocal sparingly.

In [1]:
counter = 0
def increment():
    global counter
    counter += 1

increment(); increment()
print('counter:', counter)
counter: 2

6 Modules

A module is a .py file of reusable code. Import with import or from ... import. You can create your own module by writing a .py file and importing it.

In [1]:
# Create a tiny module file on disk, then import it
with open('mymods.py', 'w') as f:
    f.write('def circle_area(r):\n    return 3.141592653589793 * r * r\n')

import mymods
print('Area of r=5:', round(mymods.circle_area(5), 2))
Area of r=5: 78.54

7 Lambda Functions and map/filter

Lambdas are anonymous one-expression functions, handy with map and filter.

In [1]:
square = lambda x: x * x
print(square(6))

nums = [1, 2, 3, 4, 5]
print('squared:', list(map(lambda x: x**2, nums)))
print('evens:', list(filter(lambda x: x % 2 == 0, nums)))
36
squared: [1, 4, 9, 16, 25]
evens: [2, 4]

8 Top-Down Stepwise Refinement

Break a big problem into functions, then refine each. Example: a small statistics summary.

In [1]:
def summarize(data):
    return {
        'count': len(data),
        'mean': statistics.mean(data),
        'median': statistics.median(data),
        'stdev': statistics.stdev(data) if len(data) > 1 else 0.0,
        'min': min(data),
        'max': max(data),
    }

scores = [78, 85, 90, 62, 71, 88, 95, 80]
summarize(scores)
{'count': 8, 'mean': 81.125, 'median': 82.5, 'stdev': 10.776131031126154, 'min': 62, 'max': 95}

9 A Brief Look at Recursion

A function that calls itself needs a base case. Factorial is the canonical example.

In [1]:
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)

print('5! =', factorial(5))
print('math.factorial(5) =', math.factorial(5))
5! = 120
math.factorial(5) = 120

Case Study: A Reusable Data Summary Toolkit

Combine the helpers into a small toolkit and apply it to a synthetic dataset.

In [1]:
import random
random.seed(7)
scores = [random.randint(50, 100) for _ in range(30)]

def summarize(data):
    return {
        'count': len(data),
        'mean': round(statistics.mean(data), 2),
        'median': statistics.median(data),
        'stdev': round(statistics.stdev(data), 2),
        'min': min(data),
        'max': max(data),
    }

report = summarize(scores)
for k, v in report.items():
    print(f'{k:>8}: {v}')
   count: 30
    mean: 69.2
  median: 67.5
   stdev: 14.17
     min: 52
     max: 91

Exercises

  1. Write a function is_prime(n) returning True/False.
  2. Write celsius_to_fahrenheit(c) and its inverse.
  3. Write a function that returns both the mean and median of a list.
  4. Demonstrate the difference between mutating and rebinding a list argument.
  5. Create a module statskit.py with a summarize function and import it.
  6. Use map with a lambda to double each value in a list.
  7. Write a recursive power(base, exp).
  8. Write a function with a default argument and call it two ways.
  9. Explain why modifying a global from inside a function needs global.
  10. Refactor a 20-line script into three well-named functions.

Python Data Science: From Foundations to Applications — Chapter 4