Table of contents
Implement code functionality

How to create a function in Python

May 30, 2025
 ・ by  
Claude and the Anthropic Team
Table of contents
H2 Link Template
Try Claude

Functions in Python let you write reusable blocks of code that perform specific tasks. They help break down complex programs into manageable pieces, improve code organization, and follow the DRY (Don't Repeat Yourself) principle.

This guide covers essential techniques for creating robust Python functions, with practical examples and debugging tips created using Claude, an AI assistant built by Anthropic.

Basic function definition with def

def greet():
    print("Hello, World!")
    
greet()  # Call the function
Hello, World!

The def keyword creates a function definition in Python, establishing a reusable code block that you can call multiple times. This example demonstrates a simple function named greet() that outputs a greeting message.

Functions provide several key benefits for code organization and maintenance:

  • They encapsulate related code into logical units
  • They reduce code duplication by making functionality reusable
  • They improve readability by breaking complex operations into named components

When Python encounters the def statement, it creates a function object and assigns it to the specified name. The indented code block beneath defines what the function will do when called.

Function parameters and return values

Functions become more powerful when they can receive input data, work with default values, and handle flexible argument structures through *args and **kwargs.

Using parameters to pass data to functions

def add_numbers(a, b):
    return a + b
    
result = add_numbers(5, 3)
print(f"The sum is: {result}")
The sum is: 8

The add_numbers() function demonstrates how parameters enable functions to work with input data. When you call the function with add_numbers(5, 3), Python passes these values to the parameters a and b in the function definition.

  • The function takes two parameters that represent the numbers to add
  • The return statement sends the calculated sum back to where the function was called
  • The returned value gets stored in the result variable for later use

This pattern of passing data in, processing it, and returning results forms the foundation for writing flexible, reusable functions. You can call the same function with different values to perform the same operation on various inputs.

Using default parameter values

def greet_person(name, greeting="Hello"):
    return f"{greeting}, {name}!"
    
print(greet_person("Alice"))
print(greet_person("Bob", "Hi"))
Hello, Alice!
Hi, Bob!

Default parameters let you specify fallback values that Python uses when you don't provide an argument. In greet_person(), the greeting parameter defaults to "Hello" if you omit it when calling the function.

  • When calling greet_person("Alice"), Python uses the default greeting "Hello"
  • For greet_person("Bob", "Hi"), the explicit "Hi" argument overrides the default
  • Default values make functions more flexible while maintaining backward compatibility

You can set defaults for any parameter. However, parameters with default values must come after parameters without defaults in the function definition. This design choice helps Python avoid ambiguity when matching arguments to parameters.

Working with *args and **kwargs

def process_data(*args, **kwargs):
    print(f"Positional arguments: {args}")
    print(f"Keyword arguments: {kwargs}")
    
process_data(1, 2, 3, name="Alice", age=30)
Positional arguments: (1, 2, 3)
Keyword arguments: {'name': 'Alice', 'age': 30}

Python's *args and **kwargs syntax enables functions to accept any number of arguments with remarkable flexibility. The *args parameter collects all positional arguments into a tuple, while **kwargs gathers keyword arguments into a dictionary.

  • When process_data(1, 2, 3, name="Alice", age=30) runs, *args captures (1, 2, 3) as positional arguments
  • The **kwargs parameter simultaneously collects name and age as keyword arguments into a dictionary
  • This pattern proves especially useful when building functions that need to handle varying numbers of inputs or pass arguments to other functions

The asterisk operators (* and **) tell Python to unpack these collections of arguments. This makes your functions more adaptable without requiring fixed parameter counts.

Advanced function techniques

Building on these foundational concepts, Python offers powerful techniques like lambda functions, decorators, and recursion to write more sophisticated and efficient code.

Creating anonymous functions with lambda

square = lambda x: x ** 2
numbers = [1, 2, 3, 4, 5]
squared = list(map(square, numbers))
print(squared)
[1, 4, 9, 16, 25]

Lambda functions create small, anonymous functions in a single line of code. The example shows a lambda that takes one parameter x and returns its square using the power operator **.

  • The lambda syntax eliminates the need for a formal def statement when you need a simple function
  • The map() function applies our square lambda to each number in the list
  • Converting the map result to a list produces the final squared values

While lambdas work well for simple operations, they should only contain a single expression. Use regular functions for more complex logic that requires multiple lines or statements.

Implementing function decorators

def my_decorator(func):
    def wrapper():
        print("Before function call")
        func()
        print("After function call")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")
    
say_hello()
Before function call
Hello!
After function call

Decorators modify or enhance functions without changing their source code. The @my_decorator syntax wraps the say_hello() function inside the wrapper() function, enabling you to execute code before and after the original function runs.

  • The decorator function my_decorator takes a function as input and returns a new function that adds functionality
  • When Python sees @my_decorator, it automatically passes say_hello as an argument to my_decorator
  • The wrapper() function provides a clean way to extend behavior while maintaining the original function's integrity

This pattern proves especially useful for adding logging, performance monitoring, or access control to multiple functions without duplicating code. You'll often encounter decorators in web frameworks and testing tools.

Creating recursive functions

def factorial(n):
    if n <= 1:
        return 1
    else:
        return n * factorial(n-1)

print(f"5! = {factorial(5)}")
5! = 120

Recursive functions solve problems by calling themselves with modified inputs. The factorial() function demonstrates this elegant approach by calculating the product of a number and all positive integers below it.

  • The if n <= 1 statement serves as the base case. It stops the recursion when n reaches 1, preventing infinite loops
  • Each recursive call multiplies the current number n with factorial(n-1). This breaks down the calculation into smaller, manageable steps
  • When calculating factorial(5), the function creates a chain of operations: 5 * 4 * 3 * 2 * 1

While recursion offers an intuitive solution for mathematical operations like factorials, it can consume more memory than iterative approaches. Consider your specific use case when choosing between recursive and loop-based implementations.

Get unstuck faster with Claude

Claude is an AI assistant created by Anthropic that helps developers write better code and solve complex programming challenges. It combines deep technical knowledge with natural conversation to provide clear, actionable guidance.

When you encounter tricky Python concepts like recursion or decorators, Claude can explain them step-by-step and suggest practical implementations. It helps debug errors, reviews code for potential improvements, and answers questions about Python's standard library.

Start accelerating your Python development today. Sign up for free at Claude.ai to get personalized coding assistance and level up your programming skills.

Some real-world applications

Building on the advanced techniques we've explored, these practical examples demonstrate how Python functions streamline data processing and mathematical operations in real applications.

Processing data with functions and multiple return values

Python functions can return multiple values in a single statement, enabling efficient data analysis through tuple packing and unpacking—as demonstrated in this temperature processing example.

def analyze_temperatures(temperatures):
    avg = sum(temperatures) / len(temperatures)
    highest = max(temperatures)
    lowest = min(temperatures)
    return avg, highest, lowest

daily_temps = [72, 65, 78, 80, 68, 74, 77]
avg_temp, max_temp, min_temp = analyze_temperatures(daily_temps)
print(f"Average: {avg_temp:.1f}°F, Highest: {max_temp}°F, Lowest: {min_temp}°F")

The analyze_temperatures function processes a list of temperature readings and returns three key metrics in a single line. It calculates the average using sum() divided by len(), while max() and min() identify the highest and lowest values.

Python's tuple unpacking shines in this example. When the function returns multiple values separated by commas, you can assign them directly to separate variables. The f-string formats the output with a single decimal place for the average temperature using .1f.

  • Takes a list of temperatures as input
  • Returns three values: average, maximum, minimum
  • Uses built-in Python functions for efficient calculations

Creating a function-based calculator with lambda and dictionaries

This example combines lambda functions with a dictionary to create a flexible calculator that performs basic arithmetic operations through a clean, maintainable interface.

def calculate(operation, a, b):
    operations = {
        'add': lambda x, y: x + y,
        'subtract': lambda x, y: x - y,
        'multiply': lambda x, y: x * y,
        'divide': lambda x, y: x / y if y != 0 else "Error: Division by zero"
    }
    
    if operation in operations:
        return operations[operation](a, b)
    return "Unknown operation"

print(calculate('add', 10, 5))
print(calculate('multiply', 3, 4))
print(calculate('divide', 8, 2))

The calculate() function implements a flexible calculator using a dictionary to store mathematical operations. Each operation maps to a lambda function that performs the calculation. When you call calculate('add', 10, 5), it looks up the 'add' operation in the dictionary and executes the corresponding lambda with the provided numbers.

  • The dictionary approach eliminates the need for multiple if/else statements
  • Each lambda function concisely defines the mathematical operation in a single line
  • The function handles division by zero gracefully by returning an error message

If you pass an operation that doesn't exist in the dictionary, the function returns "Unknown operation" instead of raising an error. This makes the function more robust and user-friendly.

Common errors and challenges

Python functions can trigger subtle bugs through scope confusion, mutable defaults, and recursive depth limits. Understanding these pitfalls helps you write more reliable code.

Avoiding variable scope issues with the global keyword

Variable scope issues commonly trip up Python developers when modifying global variables inside functions. The following code demonstrates a classic UnboundLocalError that occurs when trying to modify the global total variable without properly declaring it.

total = 0

def add_to_total(value):
    total = total + value
    return total

print(add_to_total(5))

Python interprets total = total + value as creating a new local variable instead of modifying the global one. This triggers an error because the function tries to use total before assigning it. Check out the corrected version below.

total = 0

def add_to_total(value):
    global total
    total = total + value
    return total

print(add_to_total(5))

The global keyword explicitly tells Python to use the variable from the global scope instead of creating a new local one. Without it, Python assumes you want to create a local variable when you assign a value, even if a global variable exists with the same name.

  • Watch for this issue when modifying global variables inside functions
  • Consider using return values and parameters instead of global variables for cleaner code
  • The error typically appears as UnboundLocalError when you try to read a global variable before assigning it locally

While the global keyword fixes the immediate problem, it's often better to avoid global variables altogether. They can make code harder to understand and maintain.

Avoiding unexpected behavior with mutable default arguments like [] and {}

Python's mutable default arguments can create confusing behavior when you reuse functions. The default list or dictionary persists between function calls instead of creating a fresh instance each time. This code demonstrates a common trap with list defaults.

def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item("apple"))
print(add_item("banana"))

The add_item() function creates a single list object when Python defines the function. Each call modifies this same list instead of creating a new one. The code below demonstrates the proper way to handle mutable default arguments.

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

print(add_item("apple"))
print(add_item("banana"))

Using None as the default argument and creating a new list inside the function solves the mutable default argument problem. This approach ensures each function call starts with a fresh list instead of reusing the same one.

  • Watch for this issue when using lists, dictionaries, or sets as default arguments
  • The problem occurs because Python evaluates default arguments only once during function definition
  • Always initialize mutable defaults to None and create the actual data structure inside the function

This pattern works especially well for functions that need to maintain separate state for each call. It prevents unexpected behavior where data from previous calls affects current operations.

Preventing stack overflow with function memoization in recursive calls

Recursive functions can quickly consume memory and processing power when handling large inputs. The classic fibonacci() implementation demonstrates this limitation. Each recursive call creates new stack frames that accumulate until Python hits its recursion limit or runs out of memory.

def fibonacci(n):
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(35))

The fibonacci() function recalculates the same values repeatedly. For example, computing fibonacci(5) calculates fibonacci(2) three separate times. This redundancy creates exponential time complexity and slows down the program significantly.

The optimized version below demonstrates how to cache previously calculated values.

def fibonacci(n, memo=None):
    if memo is None:
        memo = {}
    if n in memo:
        return memo[n]
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
    return memo[n]

print(fibonacci(35))

The optimized fibonacci() function uses memoization to store previously calculated values in a dictionary. This caching technique prevents redundant calculations by checking if a value exists in the memo before computing it again. The memo dictionary persists between recursive calls through the function parameter.

  • Watch for performance issues in recursive functions that compute the same values multiple times
  • Consider using memoization when your recursive function has overlapping subproblems
  • The time complexity improves from exponential to linear with this optimization

This pattern works particularly well for mathematical sequences and dynamic programming problems where values depend on previous results.

Learning or leveling up? Use Claude

Anthropic's Claude combines sophisticated programming expertise with intuitive teaching abilities to guide developers through Python challenges. This AI assistant excels at breaking down complex concepts into digestible explanations while providing practical, production-ready code solutions.

Here are some prompts you can use to tap into Claude's Python function expertise:

  • Debug recursive functions: Ask "What's wrong with my recursive factorial function that's causing a stack overflow?" and Claude will suggest optimizations like memoization
  • Decorator explanation: Ask "Can you explain Python decorators with a real-world example?" and Claude will demonstrate practical use cases like logging or authentication
  • Parameter guidance: Ask "When should I use *args vs **kwargs?" and Claude will clarify their differences with concrete examples
  • Best practices: Ask "Review my function and suggest improvements for readability and performance" and Claude will provide actionable code refinements
  • Error solutions: Ask "Why am I getting UnboundLocalError in my function?" and Claude will explain scope issues and demonstrate fixes

Experience personalized Python mentorship by signing up for free at Claude.ai.

For a more integrated development experience, Claude Code brings AI assistance directly into your terminal, enabling seamless collaboration while you write and optimize Python functions.

FAQs

Additional Resources

How to comment in Python

2025-05-22
14 min
 read
Read more

How to iterate through a list in Python

2025-05-30
14 min
 read
Read more

How to make a set in Python

2025-05-30
14 min
 read
Read more

Leading companies build with Claude

ReplitCognitionGithub CopilotCursorSourcegraph
Try Claude
Get API Access
Copy
Expand