Functions and Modules

EE1021 Programming for Engineers · Lecture 3 · 2026–2027

Basri Erdogan

Lecture Goals

  1. Explain the difference between a function definition and a function call
  2. Pass data in through parameters and send results back with return
  3. Trace local variables without changing variables in the caller
  4. Reuse Lecture 2 decisions and loops inside functions
  5. Use a default argument, the math module, and a simple custom module

Retrieval Warm-Up: What Repeats?

raw_voltage_1 = 4.92
calibrated_1 = raw_voltage_1 + 0.05
print("Calibrated:", calibrated_1)

raw_voltage_2 = 5.08
calibrated_2 = raw_voltage_2 + 0.05
print("Calibrated:", calibrated_2)

Identify the repeated calculation. What information changes each time?

The calibration rule repeats; only the raw voltage changes. A function lets us name the rule and reuse it.

Part 1: Define and Call Functions

A Function Names a Reusable Recipe

Repeated calculation

calibrated_1 = 4.92 + 0.05
calibrated_2 = 5.08 + 0.05

One definition, two calls

def calibrate_voltage(raw_voltage):
    return raw_voltage + 0.05

calibrated_1 = calibrate_voltage(4.92)
calibrated_2 = calibrate_voltage(5.08)

The function separates the rule from the values used with it.

Anatomy of a Function

def calibrate_voltage(raw_voltage):
    """Return a calibrated voltage."""
    calibrated = raw_voltage + 0.05
    return calibrated
Part Example Job
keyword def begins a function definition
name calibrate_voltage identifies the function
parameter raw_voltage receives data from the caller
docstring """...""" explains the function’s purpose
body indented lines performs the work
return value calibrated sends a result back

Definition Stores; Call Executes

def calibrate_voltage(raw_voltage):
    print("Function body is running")
    return raw_voltage + 0.05

After Python reads the definition, nothing has been calibrated yet.

result = calibrate_voltage(4.92)
print(result)

The call enters the body, binds raw_voltage to 4.92, and produces 4.97.

Parameters and Arguments

def rectangle_area(length, width):
    return length * width

area = rectangle_area(5, 3)
Term In the example
parameters length, width in the definition
arguments 5, 3 in the call
returned result 15, assigned to area

Arguments are matched to parameters from left to right unless names are used explicitly.

Call Checkpoint

def scale_reading(reading, factor):
    return reading * factor

a = scale_reading(4, 2)
b = scale_reading(a, 0.5)

print(a, b)

Predict:

  1. What values are bound to reading and factor in each call?
  2. What is printed?

Check: first call: 4 and 2; second call: 8 and 0.5; output: 8 4.0.

Checkpoint 1 — Identify the Call

Part 2: Return Values and Local Scope

return Sends Data to the Caller

def celsius_to_fahrenheit(celsius):
    fahrenheit = celsius * 9 / 5 + 32
    return fahrenheit

converted = celsius_to_fahrenheit(25)
print("Fahrenheit:", converted)

The call expression celsius_to_fahrenheit(25) becomes the returned value 77.0.

Returned values can be:

  • assigned to variables
  • printed by the caller
  • used in another expression
  • passed into another function

return and print Have Different Jobs

Return a value

def double_value(value):
    return value * 2

answer = double_value(5)
print(answer)

answer becomes 10.

Only display a value

def show_double(value):
    print(value * 2)

answer = show_double(5)
print(answer)

The function displays 10, but answer becomes None.

return also ends the current function call; code after the executed return does not run.

Trace Local Scope

def add_offset(reading):
    reading = reading + 0.2
    return reading

reading = 1.5
adjusted = add_offset(reading)

print(reading, adjusted)
Moment caller reading local reading adjusted
before call 1.5 — —
function starts 1.5 1.5 —
local update 1.5 1.7 —
after return 1.5 gone 1.7

The parameter is a local name. Rebinding it does not rebind the caller’s variable.

Scope Checkpoint

def increase(count):
    count = count + 1
    print("inside:", count)
    return count

count = 3
new_count = increase(count)
print("outside:", count, new_count)

Predict both printed lines before running the program.

Check: inside: 4, then outside: 3 4. The two names belong to different scopes.

Checkpoint 2 — Return and Scope

Part 3: Package Decisions and Loops

A Function Can Make a Decision

def voltage_status(voltage):
    """Return a status for a 5 V supply."""
    if voltage < 0:
        return "Invalid"
    if voltage < 4.75:
        return "Low"
    if voltage <= 5.25:
        return "Normal"
    return "High"

Each call follows one path and executes one return.

status = voltage_status(5.10)
print(status)

The caller receives the string "Normal".

Verify the Boundaries

Predict the returned status for each call.

def voltage_status(voltage):
    if voltage < 0:
        return "Invalid"
    if voltage < 4.75:
        return "Low"
    if voltage <= 5.25:
        return "Normal"
    return "High"
Call Your prediction
voltage_status(-0.1)
voltage_status(4.74)
voltage_status(4.75)
voltage_status(5.25)
voltage_status(5.26)

Check: Invalid (below 0) · Low (just below 4.75) · Normal (exactly 4.75: 4.75 < 4.75 is false) · Normal (exactly 5.25: 5.25 <= 5.25 is true) · High (just above 5.25).

Test each edge and one step either side: < versus <= mistakes hide there. Boundary tests check the specification, not just the syntax.

A Function Can Contain a Loop

def sum_multiples(limit, divisor):
    """Return the sum of multiples up to and including limit."""
    total = 0
    for value in range(divisor, limit + 1, divisor):
        total += value
    return total

result = sum_multiples(12, 3)
print(result)

Trace the accumulator: 0 → 3 → 9 → 18 → 30.

The function packages the Lecture 2 accumulate pattern behind a descriptive name.

Compose a Small Processing Pipeline

raw = 4.72
calibrated = calibrate_voltage(raw, 0.05)
status = voltage_status(calibrated)

print(calibrated, status)
  1. calibrate_voltage() returns 4.77.
  2. That returned value becomes the argument to voltage_status().
  3. The caller prints the final values.

Each function has one job; the main code shows the overall sequence.

Default Arguments Make Common Calls Shorter

def calibrate_voltage(raw_voltage, offset=0.05):
    return raw_voltage + offset

standard = calibrate_voltage(4.92)
custom = calibrate_voltage(4.92, 0.10)
  • standard uses the default offset and becomes 4.97.
  • custom overrides the default and becomes 5.02.
  • Parameters without defaults must come before parameters with defaults.

Use a default only when one value is genuinely the normal case.

Part 4: Reuse Code from Modules

Standard Modules Provide Tested Functions

import math

angle_radians = math.pi / 4
component = math.sqrt(2) * math.sin(angle_radians)

print(component)
  • import math makes the module available.
  • math.pi, math.sqrt, and math.sin come from its namespace.
  • The prefix tells readers where each name is defined.

Modules let us reuse reliable code without copying its implementation.

Our Own File Can Be a Module

sensor_tools.py

def calibrate_voltage(raw, offset=0.05):
    return raw + offset

def voltage_status(voltage):
    if voltage < 0:
        return "Invalid"
    if voltage < 4.75:
        return "Low"
    if voltage <= 5.25:
        return "Normal"
    return "High"

main.py

import sensor_tools

value = sensor_tools.calibrate_voltage(4.92)
status = sensor_tools.voltage_status(value)

print(value, status)

Keep both files in the same folder for this Lecture 3 example.

Checkpoint 3 — Boundaries and Modules

Engineering Application: Design Before Coding

A program must process one raw voltage reading.

  1. Calibrate it with a default offset of 0.05 V.
  2. Classify it as Low, Normal, or High.
  3. Print one final report from the main code.

Before coding, decide:

  • Which two functions should exist?
  • What parameters does each need?
  • What should each return?
  • Which file should own the functions?

One good design is calibrate_voltage(raw, offset=0.05) and voltage_status(voltage) in sensor_tools.py, called from main.py.

Five-Question Checkout

Summary

  • A function definition stores a named recipe; a call executes it.
  • Parameters receive arguments, and return sends a result to the caller.
  • print() displays information but returns None.
  • Parameters and variables created in a function are local to that call.
  • Functions can package decisions, loops, and reusable problem-solving patterns.
  • Default arguments support common cases without removing flexibility.
  • Standard and custom modules organize reusable functions across files.