Skip to main content

Skill 8: Basic Function Design (Python)

1. Type Annotations

What to Teach

Type annotations specify what types of data a function accepts and returns. They serve as documentation and enable static type checking tools.

Basic Syntax

def function_name(param1: Type1, param2: Type2) -> ReturnType:
# implementation
pass

Common Types in Python

  • int - integers
  • float - decimal numbers
  • str - text values
  • bool - True/False values
  • list[Type] - lists of specific types
  • list - lists of any type

Teaching Examples

Good Example:

def calculate_tip(bill: float, rate: float) -> float:
"""calculate tip amount based on bill total and tip rate"""
return bill * rate

List Type Annotations:

def find_max(numbers: list[int]) -> int:
"""find the largest number in a list"""
return max(numbers)

def filter_positive(numbers: list[float]) -> list[float]:
"""return only positive numbers from the list"""
return [num for num in numbers if num > 0]

What to Point Out:

  • Clear parameter names that indicate purpose
  • Specific types (use int vs float when appropriate)
  • List types specify the element type: list[int] not just list
  • Return type matches what the function actually produces

Common Student Mistakes to Watch For

  1. Missing type annotations entirely

    # BAD - no type information
    def add(x, y):
    return x + y
  2. Using generic list instead of specific types

    # BAD - too generic
    def process_scores(scores: list) -> float:
    return sum(scores) / len(scores)
  3. Wrong types for operations

    # BAD - trying to multiply strings by strings
    def repeat_word(word: str, times: str) -> str:
    return word * times
  4. Inconsistent return types

    # BAD - sometimes returns int, sometimes str
    def process_score(score: int) -> int:
    if score >= 90:
    return "A"
    else:
    return score

2. Documentation (Docstrings)

What to Teach

Docstrings explain what a function does in plain English. They should be concise but complete, following Python conventions.

Python Docstring Format

def function_name(param: Type) -> ReturnType:
"""Brief description of what the function does.

Can include longer explanation if needed.
"""
pass

Guidelines for Good Docstrings

  • Purpose-focused: What does this function accomplish?
  • Not redundant: Don't repeat what the type annotation already says
  • Avoid implementation details: Focus on what, not how
  • Triple quotes: Always use """ for docstrings

Teaching Examples

Good Example:

def compound_interest(principal: float, rate: float, years: int) -> float:
"""calculate final amount after compound interest is applied annually"""
return principal * (1 + rate) ** years

Poor Examples and How to Fix Them:

# BAD - redundant with type annotation
def add_numbers(x: int, y: int) -> int:
"""takes two integers and returns an integer"""
return x + y

# FIX
def add_numbers(x: int, y: int) -> int:
"""calculate the sum of two values"""
return x + y
# BAD - includes implementation details
def is_even(n: int) -> bool:
"""uses modulo operator to check if remainder is 0 when divided by 2"""
return n % 2 == 0

# FIX
def is_even(n: int) -> bool:
"""determine whether a number is even"""
return n % 2 == 0

What to Point Out:

  • Ask students: "How would you explain this to a friend who can't see the code?"
  • If they include implementation details, ask: "Does the user need to know HOW it works?"
  • Encourage them to write the docstring before the implementation
  • Show them that good docstrings make functions easier to use

3. Test Cases with assert and pytest

What to Teach

Tests are one of our primary design tools, and serve as a way of helping students to both understand problems and precisely specify what they should do. Python uses assert statements in test functions following pytest conventions.

Basic Test Structure

def test_function_name():
assert function_name(input1) == expected_output1
assert function_name(input2) == expected_output2

Types of Test Cases to Cover

  1. Common/typical cases - normal expected inputs
  2. Edge cases - boundary values (0, negative numbers, empty lists)
  3. Corner cases - unusual but valid inputs

4. Clean Implementation

What to Teach

Function bodies should be readable, efficient, and follow good naming conventions and Python style.

Key Principles

Clear Parameter Names:

# BAD
def calc(x, y, z):
return x * y * z

# GOOD
def calculate_volume(length: float, width: float, height: float) -> float:
"""calculate volume of a rectangular prism"""
return length * width * height

Avoid Unnecessary if Statements:

# BAD - unnecessary if for boolean return
def is_positive(n: int) -> bool:
"""determine if a number is positive"""
if n > 0:
return True
else:
return False

# GOOD
def is_positive(n: int) -> bool:
"""Determine if a number is positive."""
return n > 0

Use Descriptive Variable Names:

# BAD - unclear variable names
def process_data(data: list[int]) -> float:
"""calculate something from the data"""
t = 0
c = 0
for x in data:
if x > 0:
t += x
c += 1
return t / c if c > 0 else 0

# GOOD - clear variable names
def average_positive_numbers(numbers: list[int]) -> float:
"""calculate average of positive numbers in the list."""
positive_sum = 0
positive_count = 0

for number in numbers:
if number > 0:
positive_sum += number
positive_count += 1

return positive_sum / positive_count if positive_count > 0 else 0

Assessment Checklist

When reviewing student work, check for:

Type Annotations:

  • All parameters have type annotations
  • Return type is specified
  • Types are appropriate and specific (list[int] not just list)
  • Types match the operations used

Documentation:

  • Docstring is present and meaningful
  • Explains purpose, not implementation
  • Uses triple quotes """
  • Not redundant with type information

Testing:

  • Uses assert statements
  • Tests cover common cases
  • Tests include edge cases (empty lists, zero, negative numbers)

Implementation:

  • Parameter names are clear and descriptive
  • No unnecessary if statements for boolean returns
  • Variable names are descriptive
  • Logic is correct

Common Teaching Scenarios

When Students Ask "Why do we need type annotations?"

"Type annotations help you think clearly about what your function does, help catch errors early, and make your code easier for others to understand and maintain."

When Students Write Overly Complex Docstrings

"Imagine explaining this function to a teammate in one sentence. What's the most important thing they need to know to use it?"

When Students Skip Edge Case Testing

"What happens if someone passes an empty list to your function? What about zero or negative numbers? Testing these cases now prevents bugs later."

When Students Write Messy Code

"Code is read more often than it's written. Make it easy for future you to understand. Use meaningful variable names and consistent formatting."