==================================================== 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. .. dropdown:: Exercise 1 -- First-Class Functions and Lambdas :icon: gear :class-container: sd-border-primary :class-title: sd-font-weight-bold **Goal** Demonstrate your understanding of first-class functions, lambda expressions, and built-in higher-order functions (``map``, ``filter``, ``sorted``). .. raw:: html
**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)``: .. code-block:: python people = [("Alice", 30), ("Bob", 25), ("Charlie", 35)] a) Use ``sorted()`` with a lambda to sort by age (ascending). b) 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:** .. code-block:: text === 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)] .. raw:: html
**Deliverables** - ``lecture5/lambdas_and_hof.py`` - The program must run without errors and produce output matching the expected format above. .. dropdown:: Exercise 2 -- Write Your Own Decorators :icon: gear :class-container: sd-border-primary :class-title: sd-font-weight-bold **Goal** Practice writing decorators with ``*args``/``**kwargs``, using ``functools.wraps``, and transforming return values. .. raw:: html
**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 !"`` 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:** .. code-block:: text === greet decorator === Hello from say_goodbye! Goodbye! === repeat_twice decorator === Hello Hello === uppercase_result decorator === get_name() returned: ALICE .. raw:: html
**Deliverables** - ``lecture5/custom_decorators.py`` - The program must run without errors and produce output matching the expected format above. .. dropdown:: Exercise 3 -- Closures, Callables, and Partials :icon: gear :class-container: sd-border-primary :class-title: sd-font-weight-bold **Goal** Practice closures with ``nonlocal``, the ``callable()`` built-in, and ``functools.partial`` for argument freezing. .. raw:: html
**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. .. code-block:: python 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 ``"[] "``. 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:** .. code-block:: text === 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 .. raw:: html
**Deliverables** - ``lecture5/closures_and_partials.py`` - The program must run without errors and produce output matching the expected format above. .. dropdown:: Exercise 4 -- Data Processing Pipeline :icon: gear :class-container: sd-border-primary :class-title: sd-font-weight-bold **Goal** Combine decorators, closures, partials, and higher-order functions to build a simple data processing pipeline for sensor readings. .. raw:: html
**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: .. code-block:: python readings = [15.2, -3.0, 22.8, 8.1, -1.5, 30.0, 17.6] a) Filter out negative readings using ``make_filter(0)``. b) Convert each remaining reading to Fahrenheit using ``to_fahrenheit`` with ``map``. c) Sort the Fahrenheit results using ``sorted()`` with a lambda key. d) Apply ``@log_call`` to a function that orchestrates the pipeline. e) Print each stage's output with labels. **Expected output (values should be computed dynamically):** .. code-block:: text === 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] .. raw:: html
**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.