DEV Community

Cover image for Return Multiple Values in Python Functions
Mateen Kiani
Mateen Kiani

Posted on • Originally published at milddev.com

Return Multiple Values in Python Functions

Ever found yourself needing to send back more than just one result from a function? In Python, returning multiple values is not only possible but also elegant. Yet many developers overlook the power of unpacking and structuring these returns for clearer, more maintainable code. Have you ever wondered how to choose between tuples, lists, dicts, or even more structured types when your function needs to give back a collection of results?

It all comes down to understanding each approach and its trade-offs. By learning how to pick the right return type—whether you want quick tuple unpacking, flexible lists, self-documenting namedtuples or dataclasses, or even streaming results via generators—you can write functions that communicate intent clearly, reduce bugs, and make downstream code easier to work with.

Basic Tuple Unpacking

The simplest way to return multiple values is with a tuple. Python packs comma-separated values into a tuple automatically. Unpacking makes using those values straight-forward:

def get_user_info(user_id):
    # Simulated database lookup
    name = "Alice"
    age = 30
    active = True
    return name, age, active  # returns a tuple

name, age, active = get_user_info(42)
print(name, age, active)  # Alice 30 True
Enter fullscreen mode Exit fullscreen mode

Why use tuples? They’re immutable, lightweight, and require no extra imports. Your caller can unpack in one line or assign the tuple to a single variable:

result = get_user_info(42)
print(result)  # ('Alice', 30, True)
Enter fullscreen mode Exit fullscreen mode

Tip: If you only care about some values, use _ as a placeholder when unpacking:

name, _, active = get_user_info(42)

This keeps your intent clear and your code tidy.

Lists and Dict Returns

Tuples work great for fixed sets, but sometimes you need flexibility or dynamic keys. Lists and dicts come into play when the number of return items can change or you want named access.

Using a list:

def filter_even(nums):
    return [n for n in nums if n % 2 == 0]

evens = filter_even([1, 2, 3, 4, 5, 6])
print(evens)  # [2, 4, 6]
Enter fullscreen mode Exit fullscreen mode

If you end up with nested lists, you can flatten them using tools from recipes like the Python flatten list guide.

Using a dict:

def stats(nums):
    return {
        'min': min(nums),
        'max': max(nums),
        'count': len(nums)
    }

data = stats([10, 5, 8, 3])
print(data['max'])  # 10
Enter fullscreen mode Exit fullscreen mode

Tip: A dict return is self-documenting. You don’t need to remember index positions, you just use keys.

You can iterate over or unpack a dict return easily:

for key, value in data.items():
    print(key, value)
Enter fullscreen mode Exit fullscreen mode

(See more on how to iterate dict returns.)

Namedtuple and Dataclass

When you want the brevity of tuples plus the readability of dicts, namedtuple and dataclass shine.

from collections import namedtuple
User = namedtuple('User', ['name', 'age', 'active'])

def get_user():
    return User(name='Bob', age=25, active=False)

user = get_user()
print(user.name, user.age)
Enter fullscreen mode Exit fullscreen mode

For Python 3.7+, dataclass adds type hints and default values:

from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int = 0
    active: bool = True


def get_user():
    return User('Eve', age=28)

user = get_user()
print(user)  # User(name='Eve', age=28, active=True)
Enter fullscreen mode Exit fullscreen mode

These structures give you attribute access, immutability control, and built-in methods like _asdict() for namedtuples or . __repr__() for dataclasses. They make your code more explicit and reduce errors when fields shift around.

Generator and Yield

What if you don’t want to collect everything at once? Generators let you yield values one at a time. This is useful for large data or streams:

def read_lines(filepath):
    with open(filepath) as f:
        for line in f:
            yield line.strip(), len(line)

for content, length in read_lines('data.txt'):
    print(f"{content[:10]}... ({length} chars)")
Enter fullscreen mode Exit fullscreen mode

Here, the function returns a sequence of (content, length) pairs without loading the entire file into memory.

Tip: Use next() on a generator to fetch one result or wrap it in list() to materialize all pairs.

Generators give you lazy evaluation and can return an infinite sequence as well:

def counter():
    i = 0
    while True:
        yield i
        i += 1
Enter fullscreen mode Exit fullscreen mode

Comparison Table

Method Syntax Ease Mutability Named Access When to Use
Tuple return a, b Immutable No Simple fixed returns
List return [a,b] Mutable No Dynamic length
Dict return {} Mutable Yes Self-documenting keyed results
namedtuple return X(...) Immutable Yes Lightweight, tuple-like
dataclass return X(...) Mutable/Imm Yes Structured, with type hints
Generator (yield) yield a, b n/a No Streaming or large datasets

This table helps you pick the return style that fits your use case.

Best Practices

  1. Match Intent: If the number and order of values are fixed, use tuples or namedtuples.
  2. Clarity: Choose dicts or dataclasses when you want named access and self-documenting fields.
  3. Performance: For large or lazy sequences, prefer generators to avoid memory spikes.
  4. Documentation: Always mention what your function returns in the docstring:
def stats(nums):
    """
    Returns a dict with keys 'min', 'max', 'count'.
    """
    # ...
Enter fullscreen mode Exit fullscreen mode
  1. Error Handling: Decide if your function should raise exceptions or return error flags as part of its multiple values (e.g., (result, error) style).
  2. Unpacking: When calling, unpack only what you need:
name, *_ = get_user_info(5)
Enter fullscreen mode Exit fullscreen mode

Keep your callers in mind: clear returns lead to clearer usage.

Conclusion

Returning multiple values in Python functions is a simple pattern with big benefits. Tuples keep things quick and dirty, lists and dicts offer flexibility or clarity, namedtuple and dataclass bring structure, and generators can stream data efficiently. By choosing the right approach, you communicate intent to fellow developers, reduce bugs, and make your codebase easier to maintain. Next time you write a function that needs to hand back more than one thing, pause and pick the style that fits best. Your future self (and your team) will thank you.

Top comments (0)