Return Values and Multiple Returns

Learn how to effectively use return values and handle multiple return scenarios in Python functions.

40 minutes Beginner Functions

Basic Return Values

Understanding Single Return Values

Definition

A return value is the output that a function sends back to the code that called it. It can be any Python object.

Explanation

The return statement ends the function execution and sends a value back to the caller. If no return statement is specified, the function returns None by default.

def square(number):
    """Return the square of a number."""
    return number ** 2

# Using the return value
result = square(5)
print(result)  # Output: 25

# Function without return statement
def greet(name):
    print(f"Hello, {name}!")

result = greet("Alice")  # Output: Hello, Alice!
print(result)  # Output: None

Multiple Return Values

Returning Multiple Values

Definition

Python functions can return multiple values as a tuple, which can be unpacked into separate variables.

Explanation

When you return multiple values, Python automatically packs them into a tuple. You can unpack these values into separate variables when calling the function.

def get_coordinates():
    """Return x and y coordinates."""
    return 10, 20

# Unpacking the return values
x, y = get_coordinates()
print(f"X: {x}, Y: {y}")  # Output: X: 10, Y: 20

# Using the tuple directly
coords = get_coordinates()
print(coords)  # Output: (10, 20)

Conditional Returns

Returning Different Values Based on Conditions

Definition

Functions can return different values based on conditions using if-else statements.

Explanation

You can use conditional logic to determine what value to return. This is useful for functions that need to handle different cases or validate input.

def absolute_value(number):
    """Return the absolute value of a number."""
    if number >= 0:
        return number
    else:
        return -number

print(absolute_value(5))   # Output: 5
print(absolute_value(-5))  # Output: 5

def check_number(num):
    """Return different messages based on number value."""
    if num > 0:
        return "Positive"
    elif num < 0:
        return "Negative"
    else:
        return "Zero"

Early Returns

Using Return Statements to Exit Early

Definition

Early returns allow you to exit a function before reaching its end, often used for validation or error handling.

Explanation

Using early returns can make your code more readable by handling edge cases first and reducing nesting. This is often called the "guard clause" pattern.

def divide(a, b):
    """Divide a by b with error handling."""
    if b == 0:
        return "Error: Division by zero"
    
    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
        return "Error: Both arguments must be numbers"
    
    return a / b

print(divide(10, 2))    # Output: 5.0
print(divide(10, 0))    # Output: Error: Division by zero
print(divide("10", 2))  # Output: Error: Both arguments must be numbers

Return Type Hints

Documenting Return Types

Definition

Type hints allow you to specify the expected return type of a function, improving code documentation and IDE support.

Explanation

Type hints help other developers understand what type of value a function returns. They also enable better IDE support and static type checking.

from typing import List, Tuple, Union

def calculate_stats(numbers: List[float]) -> Tuple[float, float]:
    """Calculate mean and median of a list of numbers."""
    if not numbers:
        return 0.0, 0.0
    mean = sum(numbers) / len(numbers)
    sorted_nums = sorted(numbers)
    mid = len(sorted_nums) // 2
    median = (sorted_nums[mid] + sorted_nums[~mid]) / 2
    return mean, median

def process_data(data: Union[str, int]) -> Union[str, int]:
    """Process data and return the same type."""
    if isinstance(data, str):
        return data.upper()
    return data * 2

Return Values in Recursion

Handling Return Values in Recursive Functions

Definition

Recursive functions use return values to build up their final result through multiple function calls.

Explanation

In recursive functions, each recursive call returns a value that contributes to the final result. The base case returns a simple value, while recursive cases combine their return values with the results of deeper calls.

def factorial(n: int) -> int:
    """Calculate factorial using recursion."""
    if n == 0 or n == 1:
        return 1
    return n * factorial(n - 1)

def fibonacci(n: int) -> int:
    """Calculate nth Fibonacci number."""
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(factorial(5))    # Output: 120
print(fibonacci(7))    # Output: 13

Return Values in Generators

Using Return in Generator Functions

Definition

Generator functions use yield to produce values and can use return to signal completion or provide a final value.

Explanation

In generator functions, yield produces values one at a time, while return can be used to signal the end of iteration or provide a final value that can be accessed through the StopIteration exception.

def count_up_to(n: int):
    """Count up to n and return the sum."""
    total = 0
    for i in range(n):
        total += i
        yield i
    return total

gen = count_up_to(5)
for num in gen:
    print(num)  # Output: 0, 1, 2, 3, 4

try:
    next(gen)
except StopIteration as e:
    print(f"Final sum: {e.value}")  # Output: Final sum: 10

Return Values in Classes

Method Return Values

Definition

Class methods can return values to provide information about the object's state or the result of operations.

Explanation

Methods in classes can return values to communicate the result of operations, provide access to object attributes, or indicate the success/failure of operations.

class BankAccount:
    def __init__(self, balance: float):
        self.balance = balance
    
    def deposit(self, amount: float) -> bool:
        """Deposit money and return success status."""
        if amount > 0:
            self.balance += amount
            return True
        return False
    
    def get_balance(self) -> float:
        """Return current balance."""
        return self.balance

account = BankAccount(1000)
success = account.deposit(500)
print(f"Deposit successful: {success}")  # Output: Deposit successful: True
print(f"New balance: {account.get_balance()}")  # Output: New balance: 1500.0

Return Values in Decorators

Handling Return Values in Decorated Functions

Definition

Decorators can modify, enhance, or replace the return value of the functions they decorate.

Explanation

Decorators can intercept the return value of decorated functions, modify it, or return a completely different value. This is useful for adding functionality like caching, logging, or validation.

def log_return(func):
    """Decorator to log function return values."""
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned: {result}")
        return result
    return wrapper

def cache_result(func):
    """Decorator to cache function results."""
    cache = {}
    def wrapper(*args, **kwargs):
        key = str(args) + str(kwargs)
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]
    return wrapper

@log_return
@cache_result
def fibonacci(n):
    """Calculate nth Fibonacci number."""
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

Return Value Best Practices

Guidelines for Effective Return Values

Definition

Best practices for designing and using return values in Python functions.

Explanation

Following best practices for return values helps create more maintainable and predictable code. This includes being consistent with return types, using appropriate return values for error cases, and documenting return values clearly.

from typing import Optional, List, Dict, Any

def search_user(user_id: int) -> Optional[Dict[str, Any]]:
    """Search for a user by ID.
    
    Returns:
        Optional[Dict[str, Any]]: User data if found, None if not found
    """
    # Simulate database lookup
    users = {
        1: {"name": "Alice", "age": 30},
        2: {"name": "Bob", "age": 25}
    }
    return users.get(user_id)

def process_items(items: List[str]) -> Dict[str, int]:
    """Process a list of items and return statistics.
    
    Returns:
        Dict[str, int]: Statistics about the processed items
    """
    if not items:
        return {"count": 0, "processed": 0}
    
    return {
        "count": len(items),
        "processed": sum(1 for item in items if item.strip())
    }
Concept 1 of 10