Exercises#

This page contains four take-home exercises that reinforce the concepts from Lecture 5. Each exercise asks you to write code from scratch based on a specification – no starter code is provided.

All files should be created inside your lecture5/ workspace folder.

Exercise 1 – First-Class Functions and Lambdas

Goal

Demonstrate your understanding of first-class functions, lambda expressions, and built-in higher-order functions (map, filter, sorted).


Specification

Create a file lecture5/lambdas_and_hof.py that implements the following. Each named function must include type hints and a Google-style docstring.

  1. ``square`` – A lambda function that squares a number. Assign it to a variable called square. Test: square(5) returns 25.

  2. ``add`` – A lambda function that takes two numbers and returns their sum. Assign it to a variable called add. Test: add(3, 7) returns 10.

  3. ``is_even`` – A lambda function that checks if a number is even. Returns True if even, False otherwise. Assign it to a variable called is_even. Test: is_even(4) returns True.

  4. ``to_upper`` – A lambda function that converts a string to uppercase. Assign it to a variable called to_upper. Test: to_upper("hello") returns "HELLO".

  5. Sorting with lambdas – Given a list of tuples representing (name, age):

    people = [("Alice", 30), ("Bob", 25), ("Charlie", 35)]
    
    1. Use sorted() with a lambda to sort by age (ascending).

    2. Use sorted() with a lambda to sort by name length.

In the if __name__ == "__main__" block, call each function/lambda with example arguments and print the results with labels.

Expected output:

=== Lambda Functions ===
square(5): 25
add(3, 7): 10
is_even(4): True
is_even(7): False
to_upper("hello"): HELLO

=== Sorting ===
By age: [('Bob', 25), ('Alice', 30), ('Charlie', 35)]
By name length: [('Bob', 25), ('Alice', 30), ('Charlie', 35)]

Deliverables

  • lecture5/lambdas_and_hof.py

  • The program must run without errors and produce output matching the expected format above.

Exercise 2 – Write Your Own Decorators

Goal

Practice writing decorators with *args/**kwargs, using functools.wraps, and transforming return values.


Specification

Create a file lecture5/custom_decorators.py that implements the following decorators and test functions. Each decorator and test function must include type hints and a Google-style docstring.

  1. ``greet`` – A decorator that prints "Hello from <function_name>!" before executing the decorated function. Apply it to a function called say_goodbye() that prints "Goodbye!".

  2. ``repeat_twice`` – A decorator that executes the decorated function two times. Apply it to a function called print_message() that prints "Hello".

  3. ``uppercase_result`` – A decorator that converts the return value of a function to uppercase (assumes the function returns a string). Apply it to a function called get_name() that returns "alice".

All decorators must use @functools.wraps to preserve metadata.

In the if __name__ == "__main__" block, call each decorated function and print the results with labels.

Expected output:

=== greet decorator ===
Hello from say_goodbye!
Goodbye!

=== repeat_twice decorator ===
Hello
Hello

=== uppercase_result decorator ===
get_name() returned: ALICE

Deliverables

  • lecture5/custom_decorators.py

  • The program must run without errors and produce output matching the expected format above.

Exercise 3 – Closures, Callables, and Partials

Goal

Practice closures with nonlocal, the callable() built-in, and functools.partial for argument freezing.


Specification

Create a file lecture5/closures_and_partials.py that implements the following. Each function must include type hints and a Google-style docstring.

  1. ``make_accumulator`` – A closure that takes an initial value and returns a function. Each call to the returned function adds its argument to a running total and returns the new total.

    acc = make_accumulator(100)
    print(acc(10))  # 110
    print(acc(20))  # 130
    
  2. ``log_message`` – A general logging function with the signature log_message(level: str, msg: str) -> str that returns a formatted string "[<level>] <msg>". Use functools.partial to create a function log_info with level fixed to "INFO".

In the if __name__ == "__main__" block, demonstrate both tasks with labeled output.

Expected output:

=== Accumulator (closure) ===
acc(10): 110
acc(20): 130
acc(5): 135

=== Partial: log_info ===
log_info("System started"): [INFO] System started
log_info("Sensor ready"): [INFO] Sensor ready

Deliverables

  • lecture5/closures_and_partials.py

  • The program must run without errors and produce output matching the expected format above.

Exercise 4 – Data Processing Pipeline

Goal

Combine decorators, closures, partials, and higher-order functions to build a simple data processing pipeline for sensor readings.


Specification

Create a file lecture5/pipeline.py that implements the following. Every function must have type hints and a Google-style docstring.

  1. ``log_call`` – A decorator that prints the function name when called. Use @functools.wraps.

  2. ``make_filter(threshold)`` – A closure that returns a function. The returned function takes a list of numbers and returns only those above the threshold.

  3. ``convert_temp`` – A function with the signature convert_temp(value: float, from_scale: str, to_scale: str) -> float that converts between Celsius and Fahrenheit. Use functools.partial to create to_fahrenheit (from Celsius to Fahrenheit) and to_celsius (from Fahrenheit to Celsius).

  4. Pipeline – In the if __name__ == "__main__" block:

    readings = [15.2, -3.0, 22.8, 8.1, -1.5, 30.0, 17.6]
    
    1. Filter out negative readings using make_filter(0).

    2. Convert each remaining reading to Fahrenheit using to_fahrenheit with map.

    3. Sort the Fahrenheit results using sorted() with a lambda key.

    4. Apply @log_call to a function that orchestrates the pipeline.

    5. Print each stage’s output with labels.

Expected output (values should be computed dynamically):

=== Data Processing Pipeline ===
Calling: process_readings
Raw readings: [15.2, -3.0, 22.8, 8.1, -1.5, 30.0, 17.6]
After filtering (> 0): [15.2, 22.8, 8.1, 30.0, 17.6]
Converted to Fahrenheit: [59.36, 73.04, 46.58, 86.0, 63.68]
Sorted (ascending): [46.58, 59.36, 63.68, 73.04, 86.0]

Deliverables

  • lecture5/pipeline.py

  • The program must run without errors and produce output matching the expected format above.

  • All calculations must be computed dynamically (no hard-coded results).

  • Every function must include type hints and a Google-style docstring.