Homework

10.3.3. Homework#

  • Use these questions after class to check your understanding of the chapter concepts and coding skills.

  • Sign in from the account menu before you start working on the homework.

  • Complete all true/false and coding questions before you submit the homework.

  • Launch Live Code (the rocket) to write and run code in the coding cells.

  • Multiple submissions are allowed. The highest score is kept.

1. First-Class Functions

Passing a scoring function into a sorting helper is possible because Python functions are first-class values.

Hide code cell source

### Solution
# True
# In Python, functions are objects that can be stored in variables and passed as
# arguments. That is why you can pass a scoring function to a helper, as in sorted(items,
# key=score).

2. Pure Functions

A function that returns a transformed list without changing the original list is closer to a pure function than one that mutates shared state.

Hide code cell source

### Solution
# True
# A pure function returns a result that depends only on its arguments and has no side
# effects. Returning a new list leaves the original untouched, while mutating shared state
# is a side effect.

3. Lambda

A lambda expression is the best place for a long algorithm with several statements and detailed error handling.

Hide code cell source

### Solution
# False
# A lambda can contain only a single expression, with no statements such as try/except,
# loops, or assignments. Long logic with error handling belongs in a named function
# defined with def.

4. Comprehensions

A list comprehension is a concise way to transform or filter a collection when the logic stays readable.

Hide code cell source

### Solution
# True
# A list comprehension such as [x * 2 for x in nums if x > 0] transforms and filters in
# one expression. If the logic needs several conditions or steps, a regular loop is often
# easier to read.

5. Decorators

A decorator changes source code text in the file before Python can run the function.

Hide code cell source

### Solution
# False
# A decorator does not edit the source file. When Python runs the def statement,
# @decorator passes the function object to the decorator and binds the name to the
# function that the decorator returns.
### 6. Use a list comprehension
# Create high scores of at least 80. Print exactly this line:
# # High scores: [92, 88]
scores = [72, 92, 65, 88]

### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
scores = [72, 92, 65, 88]
high_scores = [score for score in scores if score >= 80]
print("High scores:", high_scores)
High scores: [92, 88]
### 7. Use map with a lambda
# Convert prices to cents. Print exactly this line:
# # Cents: [1250, 399, 800]
prices = [12.5, 3.99, 8.0]

### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
prices = [12.5, 3.99, 8.0]
cents = list(map(lambda price: int(price * 100), prices))
print("Cents:", cents)
Cents: [1250, 399, 800]
### 8. Use filter with a predicate
# Define `is_active` and filter records. Print exactly this line:
# # Active users: ['Ana', 'Chen']
records = [("Ana", True), ("Bob", False), ("Chen", True)]

### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
records = [("Ana", True), ("Bob", False), ("Chen", True)]

def is_active(record):
    return record[1]

active_users = [name for name, active in filter(is_active, records)]
print("Active users:", active_users)
Active users: ['Ana', 'Chen']
### 9. Log calls with a decorator
# Write a decorator log_calls(func). Its wrapper takes one argument n,
# calls func(n), appends the result to call_log, prints
# f"Call {len(call_log)}: {n} -> {result}", and returns the result.
# Apply it with @log_calls to a function square(n) that returns n * n.
# Call square(3) and square(5), then print call_log.
# Print exactly these lines:
# # Call 1: 3 -> 9
# # Call 2: 5 -> 25
# # Results: [9, 25]
call_log = []

### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
call_log = []

def log_calls(func):
    def wrapper(n):
        result = func(n)
        call_log.append(result)
        print(f"Call {len(call_log)}: {n} -> {result}")
        return result
    return wrapper


@log_calls
def square(n):
    return n * n


square(3)
square(5)
print("Results:", call_log)
Call 1: 3 -> 9
Call 2: 5 -> 25
Results: [9, 25]
### 10. Write a context manager
# Import contextmanager from contextlib. Write a generator function
# log_block(label) decorated with @contextmanager. It prints
# f"Start {label}", then yields inside try, and prints f"End {label}"
# in the finally clause so the end line prints even when the block
# raises an error.
# First, use with log_block("load"): to print "Loading 3 rows".
# Then, inside try/except ValueError as error, use
# with log_block("parse"): to raise ValueError("bad row"), and in the
# except clause print f"Caught: {error}".
# Print exactly these lines:
# # Start load
# # Loading 3 rows
# # End load
# # Start parse
# # End parse
# # Caught: bad row

### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
from contextlib import contextmanager

@contextmanager
def log_block(label):
    print(f"Start {label}")
    try:
        yield
    finally:
        print(f"End {label}")


with log_block("load"):
    print("Loading 3 rows")

try:
    with log_block("parse"):
        raise ValueError("bad row")
except ValueError as error:
    print(f"Caught: {error}")
Start load
Loading 3 rows
End load
Start parse
End parse
Caught: bad row