Python Function Arguments & Return Values: Complete Guide

Technogic profile picture By Technogic
Thumbnail image for Python Function Arguments & Return Values: Complete Guide

Introduction

Functions are one of the most powerful tools in Python, allowing you to encapsulate logic and reuse it efficiently. But what makes functions truly dynamic is their ability to accept input and return output.

This post focuses on two essential aspects of Python functions:

  1. Function Arguments – how you provide data to a function so it can perform tasks based on that input.

  2. Return Values – how a function gives back a result after completing its operation.

Whether you're building a calculator, handling user input, processing data, or automating tasks, the ability to pass information into a function and receive results back is fundamental. You'll explore different types of arguments like positional, keyword, default, and variable-length arguments (*args and **kwargs). You'll also learn how to return single or multiple values from a function.

By the end of this post, you’ll be able to write functions that are flexible, reusable, and capable of handling a wide range of inputs and outputs—essential skills for writing clean and effective Python code.

Parameters vs. Arguments

Understanding the difference between parameters and arguments is crucial for writing clear and effective functions in Python. They may sound similar, but they have distinct roles:

What Are Parameters?

  • Parameters are the named variables you list inside a function’s definition.

  • Think of them as placeholders for the values that the function will eventually use.

  • These are also known as formal parameters.

Example:

def greet(name, age):
    ...
  • Here, name and age are parameters—they define what kind of information your function needs.

What Are Arguments?

  • Arguments are the actual values you provide when calling the function.

  • These are sometimes called actual parameters, and they fill in the placeholders defined by the parameters.

Example:

greet("Alice", 30)
  • "Alice" and 30 are arguments—they get plugged into name and age when the function runs.

Quick Comparison

Term

Defined at

Role

Example

Parameter

Function definition

Placeholder for values (formal)

name, age in def greet(name, age):

Argument

Function call

Actual value passed at runtime (actual)

"Alice", 30 in greet("Alice", 30)

  • Mnemonic: Parameters are in the definition; Arguments are in the call.

Real-World Analogy

From Reddit:

A parameter represents a requirement. An argument represents an attempt to satisfy the requirement. (source)

Another puts it simply:

Parameters are placeholders; arguments are values.

That means:

  • Parameters define what the function needs (e.g., name, age).

  • Arguments are what you actually give it (e.g., "Alice", 30).

Why It Matters

Distinguishing the two helps when:

  • Reading error messages like TypeError: greet() missing 1 required positional argument.

  • Writing clean, self-documenting code.

  • Collaborating with others or maintaining shared codebases.

Example Wrapped Up

def multiply(x, y):  # x, y are parameters
    print(x * y)

# Function calls:
multiply(3, 4)       # Arguments: 3 and 4
multiply(a, b)       # Arguments: a and b (if those are variables)
  • x, y — placeholders defined by the function.

  • 3, 4 — actual inputs (arguments) used during that call.

Key Takeaway

  • Parameters = placeholders inside the function definition.

  • Arguments = the real values passed when the function is called.

Mastering this distinction will help you read and write functions more naturally and avoid common mistakes. Up next: exploring different argument styles—like positional, keyword, and variable-length—and how to get results out of your functions using return values.

Types of Arguments (In-Depth)

Python functions offer several ways to pass information into them, making your code flexible and adaptable. Here are the three most commonly used argument types:

Positional Arguments

These are the simplest and most intuitive. Values are passed in the same order as parameters appear in the function definition.

def add(a, b, c):
    print(a + b + c)

add(1, 2, 3)  # a=1, b=2, c=3
  • The first argument goes into a, the second into b, and the third into c.

  • Important: Order matters—if you swap them, you get a different result.

Keyword (Named) Arguments

With keyword arguments, you explicitly specify which parameter each value corresponds to. This improves readability and allows you to pass them in any order:

def configure_volume(left, right, mute=False):
    print(left, right, mute)

configure_volume(right=70, left=50)  # mute remains False
  • This makes code clearer—any reader can instantly see what each value means.

  • Keyword args can be mixed with positional args, but all positional ones must come first .

Default Arguments

Default arguments provide optional parameters with predefined values when no input is given.

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")         # "Hello, Alice!"
greet("Bob", "Hi")     # "Hi, Bob!"
  • Here, greeting defaults to "Hello" unless overridden.

  • ⚠️ Be careful with mutable defaults (e.g., lists): they are evaluated only once at definition time. This can cause unexpected behavior if modified later.

  • Safe alternative: use None and initialize inside the function body:

def append_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    print(items)
  • This ensures a fresh list for each call, avoiding the shared-mutable pitfall.

Summary

  • Positional args: fast and simple—just keep the right order.

  • Keyword args: more explicit, order doesn’t matter—great for readability.

  • Default args: combine flexibility with safety—use None defaults to avoid hidden bugs.

Mastering these argument types helps you write functions that are both user-friendly and robust. Up next: exploring variable-length arguments (*args, **kwargs) and return values to round out your toolkit!

Variable-Length Arguments: *args & **kwargs

When you need flexibility in function inputs—like an unknown number of values—Python offers powerful tools: *args and **kwargs.

*args: Multiple Positional Arguments as a Tuple

  • Prefixing a parameter with * (commonly named args) lets your function accept any number of positional arguments.

  • Inside the function, these are grouped into a tuple.

def my_sum(*args):
    print(sum(args))

my_sum(1, 2, 3, 4)  # output: 10
  • You can name the parameter anything (not just args), but * is mandatory.

  • If no extra arguments are passed, the tuple is empty .

  • Use cases:

    • When you don’t know how many arguments users will pass.

    • For wrapping or forwarding to other functions with consistent behavior.

**kwargs: Multiple Named Arguments as a Dictionary

  • Prefixing with ** (typically named kwargs) allows capturing arbitrary keyword arguments as a dictionary.

def print_info(**kwargs):
    for key, val in kwargs.items():
        print(f"{key} = {val}")

print_info(name="Alice", age=30)
  • You can iterate over and use these key-value pairs dynamically .

  • Useful when handling flexible configuration or passing named parameters to lower-level functions, frameworks, or libraries (e.g. GUI widget settings).

Common Usage Together

You can combine both in a single function:

def connect(host, port, *args, **kwargs):
    print(f"host={host}, port={port}")
    print("Extra positional:", args)
    print("Options:", kwargs)

connect("localhost", 8080, "user", retry=3, timeout=5)
  • Correct order in signature:

    1. Regular positional parameters

    2. *args

    3. Keyword-only parameters (if any)

    4. **kwargs.

  • This pattern preserves clarity while allowing maximum flexibility.

Why Use Them?

  • Separation & reuse: Hand off unknown arguments to other functions effortlessly.

  • Generic interfaces: Build flexible APIs that accept a range of options.

  • Dynamic behavior: Adapt based on whether specific keys are included .

Best Practices

  • Stick to *args and **kwargs naming; it’s a clear Python convention.

  • Don’t overuse them—explicit parameters are preferred when possible. Reserve *args/**kwargs for genuine flexibility .

  • Maintain correct parameter order to avoid unexpected errors .

Mastering *args and **kwargs lets you write flexible, clean, and DRY code—perfect for writing adaptable APIs or wrapper functions.

Positional-Only & Keyword-Only Arguments (Advanced)

Python allows fine-grained control over how arguments are passed to functions. Two powerful, but more advanced, patterns are positional-only and keyword-only arguments.

Positional-Only Arguments: Use the /

  • By placing a forward slash (/) in the function definition, you mark all parameters before it as positional-only. These parameters cannot be passed by name.

  • Example:

    def rectangle_area(length, width, /):
        print(length * width)
    
    rectangle_area(10, 5)          # ✅ works
    rectangle_area(length=10, width=5)  # ❌ TypeError

    This mirrors behavior in built-in functions like pow(x, y, /).

Why Use It?

  • Ensures arguments are passed positionally, avoiding confusion over parameter names.

  • Maintains compatibility with functions whose implementation may rename parameters later.

Keyword-Only Arguments: Use the *

  • Placing * in the signature forces all parameters after it to be passed by keyword only.

  • Example:

    def connect(host, port=80, *, timeout=5, ssl=False):
        ...

    Here, timeout and ssl must be named explicitly:

    connect("example.com", timeout=10, ssl=True)  # ✅ works
    connect("example.com", 8080, 10, True)        # ❌ TypeError

Why Use It?

  • Helps readability by making code clearer.

  • Prevents bugs that arise from passing options in the wrong order.

Combining Both: / and * Together

You can mix both controls in one signature:

def func(a, b, /, c, *, d):
    ...
  • a, b are positional-only.

  • c can be positional or keyword.

  • d is keyword-only.

When to Use Them

  • Use positional-only when parameter names shouldn't be part of the function’s public interface, or to maintain future compatibility.

  • Use keyword-only to enforce clarity and prevent accidental assignment ordering issues.

  • These features are more common in library and API design, and less so in everyday scripting.

Summary Table

Feature

Symbol Placement

Argument Requirement

Positional-Only

Before /

Must be passed by position (not name)

General

Between / and *

Can be positional or keyword

Keyword-Only

After *

Must be passed by name

Practical Tip

Use these advanced argument controls sparingly. They're ideal for public APIs or library code but usually unnecessary for simpler scripts.

Next, let’s explore how functions return values, either singly or as multiple outputs.

Return Values

Functions can send data back to the part of the program that called them using the return statement. Here’s how return values work in Python:

Explicit return

  • Use the return keyword followed by an expression to end a function and send a value back:

    def return_42():
        return 42
    
    result = return_42()  # result is now 42

    This is a core Python feature—functions always return something, even if it's None.

Implicit None Return

  • If you don't include return, or use return with no expression, the function returns None by default:

    def print_hello():
        print("Hello")
    
    x = print_hello()  # prints "Hello"
    print(x)           # prints "None"

    Python silently returns None if it reaches the end of a function without hitting an explicit return .

Returning Multiple Values via Tuples

  • You can return several values separated by commas. Internally, Python creates a tuple and returns it:

    def get_stats(nums):
        return min(nums), max(nums), sum(nums)/len(nums)
    
    min_val, max_val, avg = get_stats([1, 2, 3, 4])
    print(min_val, max_val, avg)  # 1 4 2.5

    Though it feels like returning multiple values, it’s actually returning a single tuple that’s unpacked by the caller.

Return Values vs. Printing

  • return passes data back to the calling code, letting it reuse the result.

  • print() just shows output on the screen and returns None, which is usually not useful for program logic.

def multiply(a, b):
    return a * b

product = multiply(3, 5)  # product = 15

Why Return Values Matter

  • They allow you to chain operations, store results, and make decisions based on function output.

  • Keeping print() and return separate makes functions more testable, reusable, and cleanly integrated into larger programs.

Summary

Case

Example

Returns

Explicit return

return value

value

Implicit return

No return or return alone

None

Multiple values via tuple

return x, y, z

(x, y, z)

Understanding return values is critical for writing reusable, composable, and test-friendly functions. In the next section, we’ll learn how to implement best practices when combining arguments and returns.

Argument & Return Best Practices

Writing functions that handle arguments and return values effectively can significantly improve code clarity, robustness, and maintainability. Here are some tried-and-true guidelines:

Descriptive Parameter Naming

  • Choose meaningful names that clearly express the purpose of each parameter.

    • ✔️ Good: def calculate_area(radius):

    • Bad: def calc(r):

  • Encourage self-documenting code—future readers (and you!) will thank you.

Avoid Mutable Default Arguments

  • Never use mutable objects (like lists or dicts) as default argument values: Python evaluates them only once at function definition, not on every call.

  • Wrong:

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

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

    This ensures a new list is created each call.

Consistent return Statements

  • According to PEP 8, maintain consistency in your return logic:

    • If any branch returns a value, all branches should do so (or at least end with return None).

  • Clear and consistent:

    def process(x):
        if invalid(x):
            return None
        return compute(x)
  • Avoid mixing implicit and explicit returns, which can lead to confusion.

Limit Function Inputs

  • Keep your function signatures simple and manageable. If you find yourself adding over 5–6 parameters, consider grouping related data into a class or dictionary.

  • For flexible argument passing, lean on *args and **kwargs, but use them sparingly and purposefully.

Validate and Handle Errors Gracefully

  • Check input validity early in the function. Raise appropriate exceptions or return meaningful error values as needed.

    def sqrt(x):
        if x < 0:
            raise ValueError("Cannot compute square root of negative number")
        return x ** 0.5
  • Avoid letting your function fail silently—explicit validation helps with debugging and reliability.

Follow PEP 8 for Readability

  • Add blank lines to separate logical blocks inside your function, enhancing clarity.

  • Keep lines under 79 characters and use consistent indentation.

  • Use linters like Flake8 or autoformatters like Black to maintain style consistency.

Summary

  • Name clearly: parameters should reflect their roles.

  • Avoid mutable defaults: default to None and instantiate inside.

  • Keep returns consistent: same pathways return comparable types.

  • Fewer, cleaner inputs: avoid bloated function signatures.

  • Validate inputs: catch issues early with clear feedback.

  • Style matters: organize code for readability and maintainability.

Applying these best practices ensures your functions are clear, safe, and easy to reuse or test.

Conclusion

Understanding how to work with function arguments and return values is a fundamental skill in Python programming. It allows you to write functions that are not only reusable but also adaptable to a wide range of inputs and use cases.

You’ve learned about:

  • The difference between parameters (placeholders) and arguments (actual values),

  • The various types of arguments, including positional, keyword, and default,

  • How to handle flexible inputs using *args and **kwargs,

  • The use of positional-only and keyword-only arguments for better control and clarity,

  • And how to use return statements to send results back to the calling code.

By applying best practices—like avoiding mutable default arguments, naming your parameters descriptively, and validating inputs—you can build functions that are clean, robust, and easy to maintain.

Mastering these concepts sets a strong foundation for writing modular, scalable Python code as you continue your programming journey.