Python Multiple Return Values: Tuples, Namedtuples, and Dataclasses
python multiple return values: Learn how to return multiple values from a Python function using tuples, namedtuples, dataclasses, and other containers, with practical...
When a Python function needs to return more than one value, the language does not provide a dedicated multi-return syntax. Instead, you package the values into a single container and unpack them at the call site. The most common container is a tuple, but the choice of container affects readability, type safety, and maintainability. This article covers the main patterns for handling python multiple return values and explains when each approach makes sense in production code.
The Basic Approach: Returning a Tuple
The simplest way to return multiple values is to place them in a tuple and return it. Python's syntax makes this almost invisible because commas create tuples without requiring parentheses.
def get_user(): return "alice", 30
The function returns a tuple ("alice", 30). At the call site, you can assign the result to a single variable or unpack it directly.
user = get_user() print(user[0]) # alice print(user[1]) # 30
Unpacking is more common and reads better:
name, age = get_user() print(name, age)
This pattern works because Python's assignment operator supports iterable unpacking. The tuple is the default container for this purpose, and it carries no extra overhead beyond the tuple object itself.
Unpacking Returned Values
Unpacking works with any iterable, not just tuples. If a function returns a list or a generator, the same syntax applies. However, tuples are the conventional choice because they are immutable and have a minimal memory footprint.
When unpacking, the number of variables must match the number of elements returned. A mismatch raises a ValueError.
def get_coordinates(): return 10, 20 x, y = get_coordinates() # works x, y, z = get_coordinates() # ValueError: not enough values to unpack
You can use the underscore _ to ignore values you do not need:
_, y = get_coordinates()
This is useful when a function returns several values but only a subset is relevant in a given context.
Using Namedtuples for Readable Returns
A plain tuple requires the caller to remember the meaning of each position. For functions that return a fixed set of fields, collections.namedtuple provides a lightweight way to give each element a name.
from collections import namedtuple User = namedtuple("User", ["name", "age"]) def get_user(): return User("alice", 30)
Now the caller can access fields by name:
user = get_user() print(user.name, user.age)
Namedtuples are still tuples under the hood, so they support indexing and unpacking. They also have a small memory overhead compared to plain tuples, but the improved readability often justifies it.
One limitation is that namedtuples are immutable. If you need to modify the returned object, you must create a new one or choose a different container.
Using Dataclasses for Structured Returns
For more complex return values that may require validation or methods, a dataclass is a better fit. Dataclasses are regular classes with type annotations and a generated __init__ method.
from dataclasses import dataclass @dataclass class User: name: str age: int def get_user(): return User(name="alice", age=30)
Dataclasses provide mutable attributes by default, which can be convenient when the caller needs to modify the returned data. They also support type hints, making the function's return type explicit and enabling static type checkers.
user = get_user() user.age = 31 # allowed
Compared to namedtuples, dataclasses have a higher per-object overhead because they are full class instances. However, they offer more flexibility: you can add methods, properties, and custom __repr__ or __eq__ behavior.
Returning Dictionaries and Lists
Sometimes a function returns a variable number of values or a set of key-value pairs. In those cases, a dictionary or a list may be more appropriate than a tuple.
def get_user_stats(): return {"posts": 10, "followers": 100} def get_scores(): return [85, 92, 78]
Dictionaries are useful when the fields are not known in advance or when you want to pass the result to functions that expect a mapping. Lists are appropriate for homogeneous sequences of arbitrary length.
However, these containers sacrifice the positional or named clarity of a tuple. A dictionary requires the caller to know the keys, and a list requires the caller to know the order. They are best used when the return structure is genuinely dynamic.
Type Hints and Return Annotations
Modern Python code uses type hints to document what a function returns. For multiple return values, you can annotate the return type as a tuple, a namedtuple, or a dataclass.
from typing import Tuple def get_user() -> Tuple[str, int]: return "alice", 30
For namedtuples and dataclasses, the class itself serves as the return type.
def get_user() -> User: return User("alice", 30)
Type hints improve maintainability by making the contract explicit. Static type checkers like mypy can catch mismatches between the returned values and the declared type.
One caveat: a bare Tuple[str, int] does not convey the meaning of each field. If the function's return value has a stable structure, prefer a namedtuple or dataclass so the type itself is self-documenting.
Performance and Maintainability Considerations
The performance difference between these return types is usually negligible for typical application code. Tuples have the lowest allocation overhead because they are simple C structures. Namedtuples add a small amount of attribute access overhead, and dataclasses add more due to their class machinery.
For functions that are called in tight loops, returning a plain tuple is the fastest option. However, the difference is rarely the bottleneck. Readability and correctness matter more in most systems.
Maintainability improves when the return type communicates its structure. A namedtuple or dataclass makes the code easier to read and reduces the chance of positional mistakes. It also allows you to add methods to the returned object, which can centralize logic that would otherwise be scattered across call sites.
When the set of fields is likely to change, a dataclass is easier to extend than a tuple. Adding a field to a tuple forces every caller to update its unpacking. With a dataclass, existing callers that use attribute access continue to work without modification.
Choosing the Right Return Type
Selecting the right container depends on the function's contract and how the caller will use the result.
- Use a plain tuple when the values are few, the order is obvious, and the function is internal or short-lived.
- Use a namedtuple when the fields are fixed and you want named access without the overhead of a full class.
- Use a dataclass when you need mutable attributes, methods, or type validation beyond simple hints.
- Use a dictionary when the keys are dynamic or when you are interoperating with JSON-like data.
- Use a list when the return value is a variable-length sequence of the same type.
For public APIs that other modules depend on, a namedtuple or dataclass is usually the safest choice. It makes the return contract explicit and reduces the risk of breaking callers when fields are added or reordered.
A final consideration: if you are using a type checker, ensure the return annotation matches the actual container. A function that returns a namedtuple but is annotated as Tuple[str, int] will still work, but it loses the named-field information that a checker could use to catch mistakes. Prefer the more specific type when it exists.