0%

30- Returning Tuples from Functions

Return multiple values from a single function. Python’s tuple packing and unpacking make multiple returns elegant and intuitive.

Most functions return one value. A number, a string, a list. But sometimes you need more. You want to return both the minimum and maximum of a list. You want to return a status code and a message. You want to return a result and an error flag. In many programming languages, you would need to create a special class or use output parameters (pass by reference). But Python has a simpler way: return a tuple. Python functions can return multiple values by simply separating them with commas. The values are automatically packed into a tuple. The caller can unpack them into separate variables in one line. This is elegant, readable, and very Pythonic.

🕯️ Magic Note

When you write return a, b, c, Python actually returns a single tuple containing (a, b, c). The parentheses are optional. The comma is what creates the tuple. This is called tuple packing. The caller’s x, y, z = func() unpack the tuple into separate variables.

Returning Multiple Values Basics
Use commas to return multiple values. No parentheses needed (though you can use them).

Python

# Return multiple values as a tuple

def get_min_max(numbers):

return min(numbers), max(numbers)

# Unpack the returned tuple into variables

minimum, maximum = get_min_max([1, 2, 3, 4, 5])

print(minimum, maximum) # 1 5

# You can also capture the tuple itself

result = get_min_max([1, 2, 3, 4, 5])

print(result) # (1, 5)

print(type(result)) # <class ‘tuple’>

💡 Returning a tuple is different from returning multiple separate values. Python always returns one object. A tuple is that one object. The unpacking syntax makes it feel like multiple returns.
Common Use Cases for Tuple Returns
Tuple returns shine in specific scenarios. Here are the most common patterns.

Python

# 1. Statistical functions (min, max, average, etc.)

def analyze_scores(scores):

return min(scores), max(scores), sum(scores) / len(scores)

lowest, highest, avg = analyze_scores([85, 92, 78, 90, 88])

print(f”Low: {lowest}, High: {highest}, Avg: {avg:.1f}”)

# 2. Status and result (like Go language style)

def divide(a, b):

if b == 0:

return False, 0 # success flag, result

return True, a / b

success, result = divide(10, 2)

if success:

print(f”Result: {result}”)

else:

print(“Division failed”)

# 3. Finding and returning a value with its index

def find_first_occurrence(items, target):

for i, item in enumerate(items):

if item == target:

return i, item

return -1, None

index, value = find_first_occurrence([10, 20, 30, 20, 40], 20)

print(f”Found {value} at index {index}”) # Found 20 at index 1

# 4. Swapping values (already uses tuple packing/unpacking)

x, y = 5, 10

x, y = y, x # Pythonic swap

print(x, y) # 10 5

🕯️ Magic Note

The pattern return success, result is common in languages like Go but is not idiomatic Python. Python programmers usually raise exceptions for errors. However, it can be useful when failures are expected and common (like searching) and you want to avoid exception overhead.

Ignoring Returned Values
You can ignore specific returned values using the underscore _ as a placeholder.

Python

def get_stats(numbers):

return min(numbers), max(numbers), sum(numbers) / len(numbers)

# Ignore min, keep max and avg

_, maximum, average = get_stats([1, 2, 3, 4, 5])

print(maximum, average) # 5 3.0

# Ignore both min and max, keep only avg

*_, average = get_stats([1, 2, 3, 4, 5])

# Or

_, _, avg = get_stats([1, 2, 3, 4, 5])

# The underscore is a convention for “unused”

💡 Use _ when you need to unpack but do not need a specific value. It signals to readers that the value is intentionally ignored.
Returning with Star Unpacking (*)
You can use * to capture remaining values when unpacking.

Python

def get_stats(numbers):

sorted_nums = sorted(numbers)

return sorted_nums[0], sorted_nums[1], sorted_nums[-2], sorted_nums[-1], sum(numbers) / len(numbers)

# Capture first, second, last, second-last, and average

first, second, second_last, last, avg = get_stats([1, 2, 3, 4, 5, 6, 7, 8, 9])

# Or use star to capture everything between

first, second, *middle, second_last, last = get_stats([1, 2, 3, 4, 5, 6, 7, 8, 9])

print(first, second, middle, second_last, last)

Named Tuples: Self-Documenting Returns
When a function returns many values, remembering the order is hard. Named tuples give names to each field.

Python

from collections import namedtuple

# Define a named tuple type

Statistics = namedtuple(“Statistics”, [“minimum”, “maximum”, “average”, “count”])

def calculate_stats(numbers):

return Statistics(

minimum=min(numbers),

maximum=max(numbers),

average=sum(numbers) / len(numbers),

count=len(numbers)

)

stats = calculate_stats([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

# Access by name (clear and self-documenting)

print(f”Minimum: {stats.minimum}”)

print(f”Maximum: {stats.maximum}”)

print(f”Average: {stats.average}”)

print(f”Count: {stats.count}”)

# Also works with unpacking

min_val, max_val, avg_val, cnt = stats

🕯️ Magic Note

Named tuples are regular tuples as well. They support indexing and unpacking. But they also give you named attributes. This is the best of both worlds: the efficiency of a tuple with the clarity of a class.

dataclasses for Complex Returns
For even more complex return values (with methods, default values, type hints), use dataclasses (Python 3.7+).

Python

from dataclasses import dataclass

from typing import List

@dataclass

class AnalysisResult:

minimum: float

maximum: float

average: float

median: float

outliers: List[float]

def is_normal_range(self):

return self.average – self.median < 0.5 * (self.maximum – self.minimum)

def analyze_data(data):

sorted_data = sorted(data)

n = len(sorted_data)

median = sorted_data[n // 2] if n % 2 else (sorted_data[n//2 – 1] + sorted_data[n//2]) / 2

return AnalysisResult(

minimum=min(data),

maximum=max(data),

average=sum(data) / n,

median=median,

outliers=[x for x in data if x > max(data) or x < min(data)]

)

result = analyze_data([1, 2, 3, 100, 4, 5, 6])

print(f”Median: {result.median}”)

print(f”Normal range? {result.is_normal_range()}”)

Returning Dictionaries (Alternative Approach)
Some programmers prefer returning dictionaries for multiple values. This is less common but has its place.

Python

# Returning a dictionary (self-documenting)

def get_user_info(user_id):

return {

“name”: “Ali Rezaei”,

“age”: 25,

“city”: “Tehran”,

“is_active”: True

}

info = get_user_info(123)

print(info[“name”]) # Ali Rezaei

# When to use dict vs tuple:

# – Tuple: Small number of values (2-5), order is meaningful

# – Named tuple: Small number of values, but names add clarity

# – Dict: Many values, or values may be added in the future

# – Dataclass: Complex return with methods or validation

Type Hints for Tuple Returns
Use type hints to document what tuple your function returns.

Python

from typing import Tuple, Union, Optional

# Simple tuple return type hint

def divide(a: float, b: float) -> Tuple[bool, float]:

if b == 0:

return False, 0.0

return True, a / b

# Tuple with mixed types

def find_user(uid: int) -> Tuple[Optional[str], Optional[int]]:

# Returns (name, age) or (None, None) if not found

if uid == 1:

return “Ali”, 25

return None, None

# Named tuple type hint (using the type itself)

def analyze(numbers: List[float]) -> Statistics:

return Statistics(min=min(numbers), max=max(numbers),

avg=sum(numbers)/len(numbers), count=len(numbers))

Common Mistakes When Returning Tuples
  • Forgetting to unpack and getting a tuple instead of separate values
  • Trying to unpack into mismatched numbers of variables (ValueError)
  • Returning too many values (more than 5-6 becomes hard to remember)
  • Modifying a returned mutable value that the function still uses
  • Using regular tuple when named tuple would be clearer

Python

# Common mistake example

def bad_return():

return 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 # Too many values

# Mistake: Forgetting to unpack correctly

result = bad_return() # result is a tuple, not 10 separate variables

a, b, c, d, e, f, g, h, i, j = bad_return() # Works but verbose

# Better: Return a named tuple for many values

ManyValues = namedtuple(“ManyValues”, “a b c d e f g h i j”)

def good_return():

return ManyValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

vals = good_return()

print(vals.a, vals.b, vals.c) # Clear and readable

Check Your Understanding
  • How do you return three values from a function?
  • What happens if you unpack a 3-value tuple into 2 variables?
  • How do you ignore a returned value?
  • What is the advantage of a named tuple over a regular tuple?
  • Write a function that returns (min, max, sum) of a list of numbers.
  • When would you return a dictionary instead of a tuple?

⚡ Whisper

Returning multiple values is like giving someone a small bundle. You tie several things together with a invisible thread: the comma. The caller receives the bundle and opens it, each item falling into its own waiting hand. This is efficient, elegant, and uniquely Pythonic. No special syntax. No wrapper classes. Just commas and assignment. But be careful. The bundle can grow heavy. If you return more than five or six values, the order becomes hard to remember. That is when you need a named bundle: a named tuple with labels on each item. Or a dictionary. Or a dataclass. The comma is for small bundles. Names are for larger ones. Choose the right wrapping for your gift. Your callers will appreciate the thought.

Related posts