Skip to main content
Python beginner Lesson 3 of 28

Python Functions and Parameters

Master defining functions, positional arguments, keyword arguments, and lambda functions.

Functions are the primary building blocks of reusable code in Python. They let you name a block of logic, call it from multiple places, and change it in one location when requirements evolve. A well-named function also documents intent — a call to calculate_area(width, height) is far clearer than the raw multiplication it wraps.

Defining Functions

Python functions are defined with the def keyword. Type hints are optional but strongly recommended — they make the function’s contract explicit and let editors and tools like mypy catch type errors before you run the code.

def calculate_area(width: float, height: float) -> float:
    """Calculate the area of a rectangle.

    The docstring is accessible via help() and shows up in editor tooltips.
    It's the primary place to document what a function does and why.
    """
    return width * height

# Call the function
area = calculate_area(5.0, 10.0)  # 50.0

Arguments and Parameters

Python’s parameter system is flexible. Understanding the different kinds of arguments lets you write functions that are both easy to call correctly and hard to call incorrectly.

Positional vs Keyword Arguments

Any function can be called with positional arguments (order matters) or keyword arguments (order doesn’t matter). Mixing both is common — positional for required values, keyword for optional ones.

# Positional — arguments matched to parameters by position
calculate_area(5.0, 10.0)

# Keyword — arguments matched by name, order is irrelevant
calculate_area(height=10.0, width=5.0)

# Mixed — positional first, then keyword
calculate_area(5.0, height=10.0)

Default Parameter Values

Default values make parameters optional and communicate what the “normal” case looks like. Parameters with defaults must come after parameters without defaults.

def greet(name: str, greeting: str = "Hello") -> str:
    """Greet a person, using 'Hello' unless a custom greeting is provided."""
    return f"{greeting}, {name}!"

greet("Alice")              # "Hello, Alice!"
greet("Alice", "Hi")        # "Hi, Alice!"
greet("Alice", greeting="Hey")  # "Hey, Alice!"

Variadic Arguments (*args and **kwargs)

*args and **kwargs let a function accept any number of arguments. This is useful for wrapper functions, decorators, and utility functions that need to work with an arbitrary number of inputs.

def sum_values(*args: float) -> float:
    # args is a tuple containing all positional arguments passed in
    # Useful when the number of inputs isn't known ahead of time
    return sum(args)

sum_values(1, 2, 3)       # 6
sum_values(1, 2, 3, 4, 5) # 15

def print_profile(**kwargs) -> None:
    # kwargs is a dict of all keyword arguments passed in
    # Useful for building flexible configuration or logging functions
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_profile(name="Alice", age=30, role="admin")
# name: Alice
# age: 30
# role: admin

Combining All Argument Types

Python has a defined order for mixing parameter kinds. The / and * markers in signatures enforce positional-only and keyword-only parameters respectively — you’ll see these in the standard library.

def create_user(
    name: str,          # required positional
    age: int,           # required positional
    *,                  # everything after * must be passed as keyword
    role: str = "user", # keyword-only with default
    active: bool = True # keyword-only with default
) -> dict:
    return {"name": name, "age": age, "role": role, "active": active}

create_user("Alice", 30)                      # uses defaults for role and active
create_user("Bob", 25, role="admin")          # overrides role
create_user("Carol", 22, role="mod", active=False)

Lambda Functions

Lambda functions are anonymous, single-expression functions defined inline. They’re most useful as short callbacks passed to functions like sorted(), map(), and filter() — cases where defining a full named function would be overly verbose.

# Standard named function
def double(x):
    return x * 2

# Lambda equivalent — same behavior, no name, no def
double_lambda = lambda x: x * 2

double(5)         # 10
double_lambda(5)  # 10

The most common use of lambdas is as the key argument to sorting functions:

users = [
    {"name": "Charlie", "age": 35},
    {"name": "Alice", "age": 28},
    {"name": "Bob", "age": 32},
]

# Sort by age — lambda extracts the sort key from each dict
sorted_users = sorted(users, key=lambda u: u["age"])
# [Alice(28), Bob(32), Charlie(35)]

# Sort strings case-insensitively
words = ["Banana", "apple", "Cherry"]
sorted_words = sorted(words, key=lambda w: w.lower())
# ["apple", "Banana", "Cherry"]

Return Values

Python functions always return a value. If no return statement is reached, the function returns None. Functions can return multiple values as a tuple, which the caller can unpack.

def min_max(numbers: list[float]) -> tuple[float, float]:
    """Return the minimum and maximum values from a list."""
    return min(numbers), max(numbers)  # returns a tuple

# Unpack the tuple directly
low, high = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print(f"Min: {low}, Max: {high}")  # Min: 1, Max: 9

Docstrings

Docstrings are string literals placed immediately after the def line. They serve as built-in documentation — accessible via help(), readable by IDE tooltips, and consumed by documentation generators like Sphinx.

def divide(a: float, b: float) -> float:
    """Divide a by b and return the result.

    Args:
        a: The dividend.
        b: The divisor. Must not be zero.

    Returns:
        The quotient of a divided by b.

    Raises:
        ValueError: If b is zero.
    """
    if b == 0:
        raise ValueError("Cannot divide by zero.")
    return a / b

Frequently Asked Questions

What are *args and **kwargs?
*args allows passing a variable number of positional arguments as a tuple. **kwargs allows passing keyword arguments as a dictionary.