โณ Loading Python Engine...

๐Ÿ“Š 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

#TopicConcept
1Functionsdef, scope, returns
2Argsargs, *kwargs
3LambdasAnonymous inline functions

1. Function Definition & Scope : Reusable Logic Blocks

๐Ÿ” What is it?

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.

In [ ]:

๐Ÿงช Concept Checks: Functions

Q1. Write a function square(n) that returns the square of n. Call it with 5 and print the result.

In [ ]:

Q2. Write a function is_even(n) that returns True if n is even, False otherwise. Test it with 4 and 7.

In [ ]:

Q3. Demonstrate local scope: define x = 10 in a function, then try to print x outside. Catch and print the NameError.

In [ ]:

Q4. Write a function greet(name, greeting="Hello"). Call it with and without the greeting argument.

In [ ]:

Q5. Write a function multiply(a, b) and call it using keyword arguments b=5, a=2. Print result.

In [ ]:

2. Arguments: *args and **kwargs : Flexible Inputs

๐Ÿ” What is it?

Python allows functions to accept an arbitrary number of arguments. args collects positional arguments into a tuple. *kwargs collects keyword arguments into a dictionary.

SyntaxCollects asExample Call
*argsTuplefunc(1, 2, 3)
**kwargsDictfunc(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):

In [ ]:

๐Ÿงช 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.

In [ ]:

Q2. Write a function print_info(**kwargs) that iterates over the kwargs dict and prints Key: Value. Call it with 3 kwargs.

In [ ]:

Q3. Write a function combine(name, args, *kwargs). Print all three parts. Call it with "Test", 1, 2, a=3, b=4.

In [ ]:

Q4. Unpack a list into a function: nums = [1, 2, 3]. Call func(*nums) instead of func(nums[0], ...). Demonstrate this.

In [ ]:

Q5. Unpack a dict into kwargs: config = {"x": 10, "y": 20}. Call func(**config). Demonstrate this.

In [ ]:

3. Recursion : Functions That Call Themselves

๐Ÿ” What is it?

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.

In [ ]:

๐Ÿงช Concept Checks: Recursion

Q1. Write a recursive function countdown(n) that prints numbers from n down to 1, then prints "Done!".

In [ ]:

Q2. Write a recursive function sum_list(lst) that returns the sum of all elements. Base case: empty list returns 0.

In [ ]:

Q3. Write a recursive function power(base, exp) that calculates base exp without using . Test with power(2, 10).

In [ ]:

Q4. Write a recursive function reverse_string(s) that reverses a string. Base case: length 0 or 1 returns s.

In [ ]:

Q5. Write a recursive flatten(lst) that flattens [1, [2, [3, 4], 5]] into [1, 2, 3, 4, 5].

In [ ]:

๐Ÿ› ๏ธ 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.

In [ ]:

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.

In [ ]:

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.

In [ ]:

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.

In [ ]:

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.

In [ ]:

๐Ÿ’ป Pure Coding Interview Questions

Q1.

Write a function that accepts any number of positional arguments and returns their average.

In [ ]:

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.

In [ ]:

Q3.

Write a recursive function flatten(nested_list) that takes a nested list like [1, [2, [3]], 4] and returns [1, 2, 3, 4].

In [ ]:

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.

In [ ]:

Q5.

Fix the mutable default argument trap in def append_val(v, lst=[]): lst.append(v); return lst. Use None as the default.

In [ ]:

Q6.

Write a function compose(f, g) that returns a new function computing f(g(x)). Test with square and add_one.

In [ ]:

Q7.

Write a simple memoization wrapper: a function memoize(func) that caches results in an inner dictionary. Test with a recursive fib(n).

In [ ]:

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').

In [ ]:

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.

In [ ]:

Q10.

Write a higher-order function apply_twice(func, x) that returns func(func(x)). Test with lambda x: x + 3 and x = 7.

In [ ]:

Q11.

Write a recursive function is_palindrome(s) that checks if a string is a palindrome without using slicing.

In [ ]:

Q12.

Sort a list of strings by their last character using a key function (not lambda). Then do it with a lambda.

In [ ]:

Q13.

Use filter and a function to remove all None or empty string values from [None, 'hello', '', 'world', None, 'hi']. Print the result.

In [ ]:

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 **.

In [ ]:

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').

In [ ]:

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).

In [ ]:

Q17.

Write a function add(a, b, /) using positional-only parameters (Python 3.8+). Show the TypeError when calling add(a=1, b=2).

In [ ]:

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.

In [ ]:

Q19.

Write a nested function demonstrating LEGB scope: define x at global, enclosing, and local level. Print x from the innermost function.

In [ ]:

Q20.

Write a recursive function sum_digits(n) that returns the sum of all digits in a positive integer. Test with 12345.

In [ ]:

Q21.

Write a function that returns (quotient, remainder) as a tuple. Unpack the result into two variables and print them.

In [ ]:

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.

In [ ]:

Q23.

Write a function merge_dicts(*dicts) that takes any number of dictionaries and merges them into one. Later dicts override earlier keys.

In [ ]:

Q24.

Import functools.partial. Create a double = partial(multiply, 2) function from def multiply(a, b): return a * b. Test it.

In [ ]:

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.

In [ ]:

๐Ÿ“Š Day 10 Executive Summary

#TopicKey Takeaway
1DefFunctions isolate logic and scope
2Args unpacks tuples, * unpacks dicts
3Lambdalambda 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.