๐ Day 10 : Functions
๐ฏ Enterprise Objective
Functions are the building blocks of maintainable code. Today we master writing flexible, reusable logic blocks using args, *kwargs, and anonymous lambda functions. Understanding these patterns is essential for writing professional-grade data pipelines.
๐ Strategic Overview
| # | Topic | Concept |
|---|---|---|
| 1 | Functions | def, scope, returns |
| 2 | Args | args, *kwargs |
| 3 | Lambdas | Anonymous inline functions |
1. Function Definition & Scope : Reusable Logic Blocks
A function is a named block of code designed to do one specific job. Defined using def, functions take inputs (arguments), perform operations, and return outputs. Variables created inside a function are in local scope and cannot be accessed from outside.
def calculate_roi(revenue, cost):
profit = revenue - cost
return (profit / cost) * 100
๐ผ Why Data Analysts Care
โข Code Reusability: Define logic once, use it across 100 datasets
โข Testability: Small functions can be individually tested to prevent pipeline bugs
โ ๏ธ Global Variables
Avoid using global variables inside functions. It creates 'spaghetti code' where state changes unpredictably. Pass data in as arguments, and return data as outputs.
๐งช Concept Checks: Functions
Q1. Write a function square(n) that returns the square of n. Call it with 5 and print the result.
Q2. Write a function is_even(n) that returns True if n is even, False otherwise. Test it with 4 and 7.
Q3. Demonstrate local scope: define x = 10 in a function, then try to print x outside. Catch and print the NameError.
Q4. Write a function greet(name, greeting="Hello"). Call it with and without the greeting argument.
Q5. Write a function multiply(a, b) and call it using keyword arguments b=5, a=2. Print result.
2. Arguments: *args and **kwargs : Flexible Inputs
Python allows functions to accept an arbitrary number of arguments. args collects positional arguments into a tuple. *kwargs collects keyword arguments into a dictionary.
| Syntax | Collects as | Example Call |
|---|---|---|
*args | Tuple | func(1, 2, 3) |
**kwargs | Dict | func(a=1, b=2) |
๐ผ Why Data Analysts Care
โข Wrapper Functions: args, *kwargs is standard for writing decorators or logging wrappers
โข Flexible APIs: Creating functions that can accept any number of column names or configuration flags
๐ง Pro Tip
Order matters: Standard args come first, then args, then kwargs. E.g., def func(a, b, args, **kwargs):
๐งช Concept Checks: *args and **kwargs
Q1. Write a function sum_all(*args) that takes any number of arguments and returns their sum. Test with 4 numbers.
Q2. Write a function print_info(**kwargs) that iterates over the kwargs dict and prints Key: Value. Call it with 3 kwargs.
Q3. Write a function combine(name, args, *kwargs). Print all three parts. Call it with "Test", 1, 2, a=3, b=4.
Q4. Unpack a list into a function: nums = [1, 2, 3]. Call func(*nums) instead of func(nums[0], ...). Demonstrate this.
Q5. Unpack a dict into kwargs: config = {"x": 10, "y": 20}. Call func(**config). Demonstrate this.
3. Recursion : Functions That Call Themselves
A recursive function is a function that calls itself to solve a problem by breaking it into smaller sub-problems. Every recursive function needs a base case (the stop condition) and a recursive case (the self-call with a simpler input).
def factorial(n):
if n <= 1: # Base case
return 1
return n * factorial(n - 1) # Recursive case
๐ผ Why Data Analysts Care
โข Tree Traversal: Navigating nested folder structures or JSON trees recursively
โข Divide and Conquer: Algorithms like merge sort and binary search use recursion naturally
โ ๏ธ Stack Overflow
Python has a default recursion limit of 1000 calls. Deep recursion will raise RecursionError. For very deep problems, convert to an iterative solution or use sys.setrecursionlimit() cautiously.
๐งช Concept Checks: Recursion
Q1. Write a recursive function countdown(n) that prints numbers from n down to 1, then prints "Done!".
Q2. Write a recursive function sum_list(lst) that returns the sum of all elements. Base case: empty list returns 0.
Q3. Write a recursive function power(base, exp) that calculates base exp without using . Test with power(2, 10).
Q4. Write a recursive function reverse_string(s) that reverses a string. Base case: length 0 or 1 returns s.
Q5. Write a recursive flatten(lst) that flattens [1, [2, [3, 4], 5]] into [1, 2, 3, 4, 5].
๐ ๏ธ Professional Practice Tasks
Theory is useless without muscle memory. Complete these tasks to solidify your understanding.
Task 1 (Math Library): Write a module-like set of functions for add, subtract, multiply, divide. divide must handle zero division. Write a master calculate(a, b, op) function that uses them.
Task 2 (Data Cleaner): Write a function clean_string(s) that trims whitespace and lowercases. Use map() to apply it to a list of messy strings.
Task 3 (Config Merger): Write a function merge_configs(default, **kwargs) that takes a default dictionary and updates it with the provided kwargs. Return the new dict.
Task 4 (Custom Sort): Given a list of strings like ['user-10', 'user-2', 'user-100'], write a lambda that extracts the integer part for correct numeric sorting.
Task 5 (Timer Wrapper): Write a simple function time_it(func, args) that records the start time, calls func(args), records end time, prints duration, and returns the result.
๐ป Pure Coding Interview Questions
Q1.
Write a function that accepts any number of positional arguments and returns their average.
Q2.
Write two functions: one that modifies a global variable using global, and one that uses a return value instead. Print both results and show why the return-value approach is safer.
Q3.
Write a recursive function flatten(nested_list) that takes a nested list like [1, [2, [3]], 4] and returns [1, 2, 3, 4].
Q4.
Write a function with a mutable default argument def append_val(v, lst=[]). Call it 3 times and print the result each time to demonstrate the bug.
Q5.
Fix the mutable default argument trap in def append_val(v, lst=[]): lst.append(v); return lst. Use None as the default.
Q6.
Write a function compose(f, g) that returns a new function computing f(g(x)). Test with square and add_one.
Q7.
Write a simple memoization wrapper: a function memoize(func) that caches results in an inner dictionary. Test with a recursive fib(n).
Q8.
Write a function filter_data(data, **kwargs) that filters a list of dicts, keeping only dicts matching ALL kwargs. Test with filter_data(users, age=25, city='NY').
Q9.
Write a function make_multiplier(n) that returns an inner function which multiplies its argument by n. Create double = make_multiplier(2) and test it.
Q10.
Write a higher-order function apply_twice(func, x) that returns func(func(x)). Test with lambda x: x + 3 and x = 7.
Q11.
Write a recursive function is_palindrome(s) that checks if a string is a palindrome without using slicing.
Q12.
Sort a list of strings by their last character using a key function (not lambda). Then do it with a lambda.
Q13.
Use filter and a function to remove all None or empty string values from [None, 'hello', '', 'world', None, 'hi']. Print the result.
Q14.
Write a function call_with_dict(func, d) that unpacks d = {'a':1, 'b':2} and passes it to func(a, b). Demonstrate with **.
Q15.
Write a function greet(name, *, greeting='Hello') that forces greeting to be keyword-only. Show a TypeError when called positionally like greet('Bob', 'Hi').
Q16.
Write a function safe_div(a, b, *, round_to=2) with a keyword-only argument. Test it with safe_div(10, 3, round_to=4).
Q17.
Write a function add(a, b, /) using positional-only parameters (Python 3.8+). Show the TypeError when calling add(a=1, b=2).
Q18.
Write a list comprehension [lambda x, i=i: x * i for i in range(5)] and call each function. Print results and explain the i=i default trick.
Q19.
Write a nested function demonstrating LEGB scope: define x at global, enclosing, and local level. Print x from the innermost function.
Q20.
Write a recursive function sum_digits(n) that returns the sum of all digits in a positive integer. Test with 12345.
Q21.
Write a function that returns (quotient, remainder) as a tuple. Unpack the result into two variables and print them.
Q22.
Write the same transformation using both list(map(str.upper, words)) and [w.upper() for w in words]. Time both and print which is faster.
Q23.
Write a function merge_dicts(*dicts) that takes any number of dictionaries and merges them into one. Later dicts override earlier keys.
Q24.
Import functools.partial. Create a double = partial(multiply, 2) function from def multiply(a, b): return a * b. Test it.
Q25.
Write a function safe_execute(func, args) that wraps func(args) in try/except and returns None on any exception. Test with a division by zero.
๐ Day 10 Executive Summary
| # | Topic | Key Takeaway |
|---|---|---|
| 1 | Def | Functions isolate logic and scope |
| 2 | Args | unpacks tuples, * unpacks dicts |
| 3 | Lambda | lambda x: x*2 is great for apply()/sort() |
โ Instructor's End-of-Day Checklist
โข [ ] I can write functions with default arguments.
โข [ ] I understand args and *kwargs.
โข [ ] I can write and use a lambda function.