Functions in Python: Part 1 - The Fundamentals
A function is a reusable block of code that performs a specific
action. Think of it like a recipe: it has a name, it takes some
ingredients (parameters), and it produces a result (return value).
Why use functions? (The "DRY" Principle)
1. Organization: Break down large, complex problems into smaller,
manageable pieces.
2. Reusability: Write the code once and use it many times. This follows
the DRY principle: Don't Repeat Yourself.
3. Readability: A program made of well-named functions is much easier
to read and understand. calculate_tax(income) is clearer than a
block of raw math.
1. Defining and Calling a Function
def keyword: Used to define a function.
Function Name: Follows standard variable naming rules
(e.g., snake_case).
Parameters: Variables listed inside the parentheses () that act as
placeholders for the data the function needs.
Colon :: Marks the end of the function header.
Indented Block: The code that makes up the function's body.
# DEFINING the function
def greet(name):
# This is the function body
print(f"Hello, {name}! Welcome.")
# CALLING the function with an argument
greet("Alice") # Output: Hello, Alice! Welcome.
greet("Bob") # Output: Hello, Bob! Welcome.
2. Parameters vs. Arguments
This is key terminology that shows you are precise.
Parameter: The variable name in the function's definition
(e.g., name in def greet(name):). It's the placeholder.
Argument: The actual value that is passed to the function when it
is called (e.g., "Alice" in greet("Alice")). It's the real data.
3. The return Statement
Functions can send a value back to the code that called them using
the return statement.
When a return statement is executed, the function immediately
stops and sends the value back.
A function can return any type of object: a number, a string, a list, a
dictionary, even another function.
If a function has no return statement, it implicitly returns None.
4. Docstrings
A docstring is a string literal that occurs as the first statement in a
function's body. It's used to document what the function does.
Syntax: Use triple quotes """...""".
Purpose: Explain the function's purpose, its arguments (Args:), and
what it returns (Returns:).
Placement Point of View (Part 1)
Clarity and Modularity: An interviewer gives you a multi-step
problem. A great candidate will break the problem down into several
small, well-named functions. This shows you can think in a
structured way.
Documentation: Writing a docstring for your functions in an
interview is a huge green flag. It shows you write professional,
maintainable code that others can understand.
None Return Value: Knowing that a function without
a return statement returns None is a common detail-oriented
question that can trip up beginners.
Functions in Python: Part 2 - Advanced Features
This is where Python's flexibility shines. Mastering these concepts is
crucial for writing clean, adaptable code and for acing technical
interviews.
1. Positional vs. Keyword Arguments
Positional Arguments: The arguments are matched to parameters
based on their order. greet("Alice", 25)
Keyword Arguments: You explicitly name the parameter you're
providing a value for. greet(name="Alice", age=25). The order no
longer matters.
def create_user(name, age, role):
print(f"User: {name}, Age: {age}, Role: {role}")
# Positional
create_user("Bob", 40, "Admin")
# Keyword (more readable and less error-prone)
create_user(role="User", name="Charlie", age=33)
# You can mix them, but positional arguments must come FIRST.
create_user("David", role="Guest", age=22)
2. Default Argument Values
You can provide a default value for a parameter. If an argument for that
parameter isn't provided during the call, the default value is used.
The Mutable Default Argument Trap (CLASSIC Interview
Question):
Rule: Default values are evaluated ONCE, when the function is
defined, not each time it's called.
Problem: If you use a mutable object like a list or dictionary as a
default, it becomes a single, shared object across all calls to that
function.
# !!! BUGGY CODE - THE TRAP !!!
def add_to_list_bad(item, my_list=[]):
my_list.append(item)
return my_list
print(add_to_list_bad(1)) # Output: [1]
print(add_to_list_bad(2)) # Expected: [2], Actual Output: [1, 2]
print(add_to_list_bad(3)) # Expected: [3], Actual Output: [1, 2, 3]
# --- THE CORRECT PATTERN ---
def add_to_list_good(item, my_list=None):
"""
If my_list is not provided, create a new empty list inside the function.
"""
if my_list is None:
my_list = []
my_list.append(item)
return my_list
print(add_to_list_good(1)) # Output: [1]
print(add_to_list_good(2)) # Output: [2]
3. Variable-Length Arguments: *args and **kwargs
This allows you to create functions that can accept any number of
arguments.
*args (Arguments): Collects extra positional arguments into
a tuple. The name args is just a convention.
**kwargs (Keyword Arguments): Collects
extra keyword arguments into a dictionary. The name kwargs is a
convention.
def flexible_function(required_arg, *args, **kwargs):
print(f"Required Argument: {required_arg}")
print("\n--- These are the *args (a tuple) ---")
for arg in args:
print(arg)
print("\n--- These are the **kwargs (a dictionary) ---")
for key, value in kwargs.items():
print(f"{key}: {value}")
flexible_function(
"I am required",
"extra_pos_1", # This goes into *args
"extra_pos_2", # This also goes into *args
student_name="Eve", # This goes into **kwargs
score=99, # This also goes into **kwargs
is_active=True # This also goes into **kwargs
4. Variable Scope (The LEGB Rule)
Scope determines where a variable can be accessed. Python searches for
a variable in this order:
1. Local: Inside the current function.
2. Enclosing: Inside any enclosing functions (for nested functions).
3. Global: At the top level of the module/script.
4. Built-in: Names pre-assigned in Python (e.g., list, print, len).