Python Assignment– 4
Functions & Functional Programming
Basic Questions
- Define a function ‘greet’ that takes a positional parameter ‘name’ and prints ‘Hello, <name>’; call ‘greet’ twice with different names.
- Define ‘add’ with two positional parameters ‘a’ and ‘b’ and a default parameter ‘sep’=’-‘; print ‘<a><sep><b>’ and return ‘a + b’; call it with positional and keyword arguments.
- Write a function ‘area_circle’ with a keyword-only parameter ‘radius’ (use a ‘*’ in the signature) and return ‘3.14159 * radius * radius’; call it using ‘radius=<value>’.
- Create ‘echo_many’ that accepts variable-length arguments ‘*args’ and prints the count and the items; call it with 1, 2, and 5 arguments.
- Create ‘make_user’ that accepts ‘**kwargs’ and returns the same dictionary; call with keys ‘name’, ‘age’, and ‘city’ and print the returned mapping.
- Write ‘stats2’ that returns two values (min and max) from two inputs; assign the multiple return values to ‘lo, hi’ and print them.
- Demonstrate local scope: define ‘demo_scope’ that sets a local ‘x = 10’ and prints it; outside, set ‘x = 99’ and show that calling ‘demo_scope’ does not change outer ‘x’.
- Demonstrate ‘global’: set a module-level ‘counter = 0’; in ‘inc’, declare ‘global counter’, increment it, and print the new value; call ‘inc’ twice and print ‘counter’.
- Demonstrate ‘nonlocal’: define ‘outer’ that sets ‘x = 0’ and defines ‘inner’ which declares ‘nonlocal x’ and increments ‘x’; call ‘inner’ three times then return ‘x’ from ‘outer’ and print it.
- Assign a function object to a variable: define ‘square(n)’ then set ‘f = square’ and call ‘f(5)’; print the result to show first-class functions.
- Pass a function as an argument: define ‘apply(fn, value)’ that returns ‘fn(value)’; pass a previously defined ‘square’ into ‘apply’ and print the result.
- Use ‘map’: given numbers ‘1..5’, apply ‘map’ with a lambda to cube each number and print the resulting list.
- Use ‘filter’: from numbers ‘1..10’, filter even numbers with a lambda and print the resulting list.
- Use ‘reduce’: import ‘functools.reduce’ and compute the product of numbers ‘1..5’ using a lambda; print the result.
- Create an inline ‘lambda’ ‘add1’ that adds 1 to its input and test it with ‘add1(10)’; print the result.
- Implement a simple callback: define ‘on_done(msg)’ that prints ‘done: <msg>’; define ‘do_work(cb)’ that calls ‘cb(“ok”)’; pass ‘on_done’ to ‘do_work’.
- Create a closure: define ‘make_adder(k)’ that returns an inner function ‘adder(n)’ capturing ‘k’ and returning ‘n + k’; make ‘add10’ and compute ‘add10(5)’.
- Write a basic decorator ‘logger’ that wraps a function, prints ‘calling <name>’, then calls and returns the original; decorate ‘square’ with ‘@logger’ and call it.
- Define a recursive function ‘sum_to(n)’ that returns the sum ‘1 + 2 + … + n’; test with ‘n = 5’ and print the result.
- Illustrate tail-recursion style: define ‘sum_to_tail(n, acc=0)’ that returns ‘acc’ when ‘n == 0’ else calls itself with ‘n-1’ and ‘acc+n’; print ‘sum_to_tail(5)’.
Intermediate Questions
- Define ‘greet_many(names, prefix=”Hi”)’ that prints ‘<prefix>, <name>’ for each name by calling an inner helper function; call with keyword argument for ‘prefix’.
- Create ‘format_user’ with positional-only ‘id’ (use ‘/’ in the signature), keyword-only ‘role’ (use ‘*’), and defaults for ‘active=True’; return a tuple ‘(id, role, active)’ and print it.
- Write ‘collect(*args, **kwargs)’ that returns a dictionary with keys ‘args’ and ‘kwargs’ holding the received values; demonstrate with mixed arguments.
- Build ‘compose(f, g)’ that returns a new function applying ‘f(g(x))’; test with lambdas ‘double’ and ‘increment’ and print ‘compose(double, increment)(3)’.
- Implement ‘apply_all(value, *funcs)’ that calls each function in order passing the previous result; use lambdas to transform a number and print the final value.
- Show variable shadowing: set ‘x = “outer”‘, define ‘foo()’ with local ‘x = “inner”‘, print both to show that the global is unchanged after ‘foo()’ runs.
- Create a closure counter: ‘make_counter(start=0)’ returns ‘inc()’ that uses ‘nonlocal current’ to add 1 and return it; call ‘inc()’ three times and print values.
- Write ‘timed’ decorator that records time before and after the wrapped call using ‘time.perf_counter()’ and prints elapsed seconds; decorate a function that loops a few times and call it.
- Write two decorators ‘tag_a’ and ‘tag_b’ that wrap string results as ‘<a>…</a>’ and ‘<b>…</b>’; chain them on ’emit_text’ and print the final string to show decorator order.
- Build ‘safe_div’ using a lambda and a default parameter to avoid division by zero: ‘lambda a, b=1: a / b’; test with ‘b=0’ inside a ‘try/except’ and print the handled message.
- Use ‘map’ with a named function ‘is_pal’ to evaluate whether strings are palindromes (case-insensitive); print the boolean list.
- Use ‘filter’ to keep dictionaries from a list where a key ‘active’ is ‘True’; print the filtered list.
- Use ‘reduce’ with a lambda to merge a list of dictionaries by summing values of the same keys; print the merged dictionary.
- Implement ‘walk_tree(node, visit)’ where ‘node’ is a nested dictionary with keys ‘value’, ‘left’, ‘right’; recursively call ‘visit(node[“value”])’ in-order; demonstrate with a tiny tree and a ‘print’ callback.
- Write ‘factorial(n)’ recursively and a ‘factorial_tail(n, acc=1)’ tail-style; print both results for ‘n = 6’.
- Demonstrate returning functions: ‘choose_op(op)’ returns a function that applies ‘+’, ‘-‘, or ‘*’ based on ‘op’; call the returned function on two inputs and print the result.
- Build ‘memoize’ decorator storing results in a dictionary keyed by args/kwargs (use ‘frozenset’ for kwargs items); decorate ‘fib(n)’ (recursive) and print ‘fib(10)’.
- Create ‘pipeline(*steps)’ returning a function that reduces an input through a sequence of functions; use with three lambdas on a number and print the final result.
- Implement a closure ‘make_limiter(limit)’ such that returned function ‘call(msg)’ prints at most ‘limit’ times using ‘nonlocal’; demonstrate with ‘limit=2’.
- Write ‘call_with_retry(fn, attempts=3, on_fail=None)’ that calls ‘fn’, catching exceptions; if all attempts fail and ‘on_fail’ is provided, call the ‘on_fail’ callback; demonstrate with a function that fails twice then succeeds.
Advanced Questions
- Build a decorator ‘retry(times=3)’ that retries the wrapped function on exceptions up to ‘times’ and returns the first successful result; decorate a function that fails randomly and print the outcome.
- Implement a decorator ‘cache’ that supports functions with ‘*args’ and ‘**kwargs’, storing results in a dictionary; decorate a slow recursive ‘fib(n)’ and print ‘fib(35)’ quickly on the second call.
- Create a parametric decorator ‘typed(expected_types)’ that asserts argument types (positional only) and raises ‘TypeError’ on mismatch; test with a function ‘dot(a, b)’ expecting two ‘list’ objects.
- Write two chained decorators ‘trace’ and ‘validate_nonnegative’ where ‘trace’ logs entry/exit and ‘validate_nonnegative’ raises on negative inputs; decorate ‘sqrt_like(x)’ that returns ‘x ** 0.5’ and show behavior on valid and invalid inputs.
- Build a higher-order function ‘make_dispatch(table)’ that returns a dispatcher function which looks up a string command in ‘table’ (a dict of ‘str’ to function) and executes it; register three lambdas and show dispatch.
- Implement a closure-based event system: ‘make_event()’ returns ‘subscribe(cb)’, ’emit(*args, **kwargs)’, and stores callbacks in a nonlocal list; subscribe two handlers and emit once.
- Write a recursive ‘flatten(lst)’ that flattens arbitrarily nested lists into a single list without using external libraries; then implement a tail-style ‘flatten_tail(lst, stack=None, acc=None)’ and compare outputs.
- Create a ‘curry’ function that transforms a binary function into a chainable form: ‘curry(add)(2)(3) -> 5’; demonstrate with ‘add’ and with a lambda.
- Implement ‘partial’ manually (similar to ‘functools.partial’) that captures some positional and keyword arguments and returns a new callable; show usage with a three-parameter function.
- Explore tail recursion limits: write ‘sum_n_recur(n)’ and ‘sum_n_tail(n, acc=0)’; call both with increasing ‘n’ until a ‘RecursionError’ occurs, catching and printing when it happens; briefly print a line noting Python’s lack of tail-call optimization.