Back to Blog
Python

How to Return Multiple Values from a Python Function

python return multiple values: Learn the practical ways to return multiple values from a Python function: tuples, dictionaries, namedtuples, and dataclasses, with trad...

pythontuple unpackingnamedtupledataclassfunction design
Illustration of a Python function returning multiple values as a tuple being unpacked into separate variables.

python return multiple values requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

In Python, a function can return only one object, but that object can be a container holding multiple values. The most common way to return multiple values is to return a tuple, which Python creates implicitly when you separate values with commas. For example, def get_point(): return 3, 4 returns a tuple (3, 4). This behavior is idiomatic and works well for small, fixed sets of values. However, as the number of values grows or the meaning of each value becomes important, other container types offer better readability and maintainability.

The Tuple Return: Python's Default Behavior

Returning a tuple is the simplest and most direct way to return multiple values. Python's syntax makes it almost invisible: you just list the values after return, separated by commas. The function actually returns a tuple object, even if you omit the parentheses.

def get_dimensions(): width = 1920 height = 1080 return width, height

The caller receives a tuple. This works for any number of values, but the order of the values becomes part of the function's contract. If you later reorder the return values, every caller that unpacks them positionally will break silently. This is the main limitation of the plain tuple approach.

Unpacking Returned Values

Tuple unpacking is the natural companion to returning multiple values. You can assign the returned tuple directly to multiple variables in one statement.

w, h = get_dimensions() print(f"{w}x{h}")

Unpacking works with any iterable, so you can also use it with lists or generators. But for return values, the tuple is the default because it is immutable and lightweight. The unpacking syntax is clear when the number of values is small and their order is obvious from the function name or documentation.

If the function returns more than a few values, unpacking becomes error-prone. For instance, a, b, c, d, e = get_stats() requires the caller to remember the exact order and count. A missing or extra value causes a ValueError at runtime, which is better than silent corruption but still a maintenance burden.

Returning a Dictionary for Named Access

When the values have distinct meanings and the caller needs to access them by name, returning a dictionary is a straightforward option. The function builds a dict and returns it.

def get_user_info(): return { "name": "Ada", "age": 36, "role": "engineer" }

The caller can access fields by key, which is self-documenting and order-independent. This works well when the set of fields may grow or the function is part of a public API where stability matters more than performance.

However, dictionaries have downsides. The keys are strings, so typos are not caught until runtime. There is no schema enforcement, and the caller must know the exact key names. Also, returning a mutable dict means the caller could accidentally modify the returned object, affecting the function's internal state if it is reused. For simple data transfer, a dictionary is often acceptable, but for more structured data, a namedtuple or dataclass is safer.

Using Namedtuple for Lightweight Named Fields

A namedtuple is a tuple subclass that allows field access by attribute name as well as by index. It is immutable, memory-efficient, and provides a clear structure for the returned values.

from collections import namedtuple Point = namedtuple("Point", ["x", "y"]) def get_point(): return Point(3, 4)

The caller can use point.x and point.y, or unpack it like a regular tuple. Namedtuples are ideal when you need a fixed set of fields, want the lightweight behavior of a tuple, and prefer attribute access over dictionary keys. They also support _asdict() to convert to a dictionary when needed.

One limitation is that namedtuple classes are defined at runtime, so type hints are less straightforward. You can use typing.NamedTuple to get better type annotation support:

from typing import NamedTuple class Point(NamedTuple): x: int y: int

This gives you a class that behaves like a namedtuple but is defined with class syntax, making it easier to document and extend.

Returning a Dataclass or Custom Class

For more complex return values that may carry behavior or validation, a dataclass is often the best choice. Dataclasses are mutable by default, but you can use frozen=True to make them immutable. They support default values, type hints, and methods.

from dataclasses import dataclass @dataclass class User: name: str age: int role: str def get_user(): return User(name="Ada", age=36, role="engineer")

Dataclasses give you a clear contract, IDE autocompletion, and the ability to add methods that operate on the data. They are more verbose than a tuple or dict, but the added structure pays off when the returned data is used across multiple modules or when the shape is likely to evolve.

A custom class without dataclass decorator works too, but requires more boilerplate for __init__, __repr__, and equality. Dataclasses eliminate that boilerplate while keeping the class extensible.

Choosing the Right Approach

The decision depends on the number of values, the need for named access, immutability, and the expected lifetime of the function's contract. The following table summarizes the tradeoffs:

ApproachNamed accessImmutableType hintsBoilerplateBest for
TupleNo (index)YesPartialMinimalSmall, fixed sets, internal use
DictionaryYes (key)NoPartialLowDynamic fields, quick access
NamedtupleYes (attr)YesGoodLowFixed fields, lightweight
DataclassYes (attr)OptionalExcellentModerateComplex data, future evolution

Use a tuple when the values are few, the order is obvious, and the function is private to a module. Use a dictionary when the fields are dynamic or the function is part of a flexible API. Use a namedtuple when you need immutability and named access without the overhead of a full class. Use a dataclass when the returned data is complex, needs methods, or will be extended over time.

Performance and Maintainability Considerations

Performance differences between these approaches are usually negligible for typical application code. Tuples and namedtuples are the most memory-efficient because they are implemented as C structures. Dictionaries have higher overhead due to hashing and dynamic resizing. Dataclasses are similar to regular objects, with a bit more overhead than tuples but still acceptable for most use cases.

The bigger concern is maintainability. A function that returns a six-element tuple forces every caller to remember the order. If you later add a field in the middle, all callers break. A namedtuple or dataclass makes the contract explicit and reduces the chance of misusing the return value. This is especially important in larger codebases where the function is called from many places.

Another consideration is serialization. If the return value needs to be converted to JSON, a dictionary is the most natural. Namedtuples and dataclasses require conversion to dict first. Dataclasses have a built-in asdict() helper, and namedtuples have _asdict(). Tuples can be converted to lists but lose field names.

Edge Cases: Generators and Conditional Returns

Sometimes you need to return multiple values but not all at once. A generator function uses yield to produce values lazily. This is different from returning a container, but it is another way to provide multiple values to the caller.

def get_coordinates(): yield 3 yield 4

Callers can iterate over the generator or use list() to collect the values. This is useful when the values are computed on demand or when the number of values is large. However, generators are single-use, so they are not a drop-in replacement for a tuple return.

Conditional returns can also cause subtle bugs. If a function sometimes returns a tuple and sometimes returns None, the caller must handle both cases. It is better to always return the same structure, or use a sentinel value, to keep the contract consistent.

def get_point(valid): if valid: return 3, 4 return None

This forces callers to check for None before unpacking. A cleaner design is to return an empty tuple or a default object, but that depends on the domain. The key is to document the behavior clearly and avoid surprising the caller with a different type than expected.

For most functions, returning a tuple or a namedtuple is sufficient. When the data grows in complexity, migrating to a dataclass is straightforward and improves long-term maintainability without significant runtime cost.

python return multiple values: Practical Usage and Code Exam | RYUSLOG DEV