Chapter 6 Functions

In Python, return and print are used for different purposes in functions:

  1. return:

    • return is used to send a value back to the caller of the function.
    • When a function is called, it can perform some operations and then use return to send a result back to where the function was called.
    • The value returned by return can be stored in a variable, used in expressions, or passed to other functions.
    • Once return is executed, the function terminates immediately.

    Example:

    def get_greeting():
        return "Hello, World!"
    
    greeting = get_greeting()
    print(greeting)  # This will print: Hello, World!
  2. print:

    • print is used to output text to the console.
    • It does not send any value back to the caller; it simply outputs the given string or other data types to the standard output (usually the console).
    • The function continues executing after print unless it encounters a return or another statement that terminates the function.

    Example:

    def display_greeting():
        print("Hello, World!")
    
    display_greeting()  # This will print: Hello, World!

6.0.1 Summary

  • Use return to send a value from the function to its caller.
  • Use print to display a value to the console.

Here is a combined example to illustrate both:

def get_and_display_greeting():
    greeting = "Hello, World!"
    print(greeting)  # This will print: Hello, World!
    return greeting

returned_value = get_and_display_greeting()
print(f"The returned value is: {returned_value}")  # This will print: The returned value is: Hello, World!

In this example, print outputs the greeting to the console, and return sends the greeting back to where the function was called, which is then stored in returned_value and printed again.

Note:Functions that do not have an explicit return expression will implicitly return the None object. The details of None will be covered in a later exercise. For the purposes of this exercise and explanation, None is a placeholder that represents nothing, or null:

6.1 Parameters and arguments

Parameters are names in a function definition; arguments are the values passed by the caller. Put required parameters first and use defaults only when one behavior is genuinely appropriate for most calls.

def calculate_total(price, quantity, discount_rate=0.0):
    """Calculate an order total after a proportional discount."""
    if quantity < 0:
        raise ValueError("quantity must be nonnegative")
    if not 0 <= discount_rate <= 1:
        raise ValueError("discount_rate must be between 0 and 1")

    subtotal = price * quantity
    return subtotal * (1 - discount_rate)


assert calculate_total(10, 3) == 30
assert calculate_total(10, 3, discount_rate=0.10) == 27

Keyword arguments make calls with several similar values easier to review. Avoid mutable defaults such as items=[], because the same object is reused across calls.

def append_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items


assert append_item("a") == ["a"]
assert append_item("b") == ["b"]

6.2 Variable-length arguments

Use *args for additional positional arguments and **kwargs for additional keyword arguments when the function truly supports an open-ended interface. An explicit signature is clearer when the accepted inputs are known.

def mean_value(*values):
    if not values:
        raise ValueError("at least one value is required")
    return sum(values) / len(values)


assert mean_value(2, 4, 6) == 4

6.3 Scope and side effects

Names assigned inside a function are local unless declared otherwise. Prefer returning a result over mutating global state; functions with explicit inputs and outputs are easier to test and reuse.

tax_rate = 0.08


def add_tax(amount, rate):
    total = amount * (1 + rate)
    return total


assert add_tax(100, tax_rate) == 108

The function reads no hidden global configuration because the rate is passed explicitly. A local variable such as total disappears after the call.

6.4 Lambda expressions

A lambda is a small anonymous function limited to one expression. It is useful for short local operations such as a sort key. Use def when logic needs a name, validation, documentation, or multiple steps.

records = [
    {"name": "Ada", "score": 95},
    {"name": "Grace", "score": 98},
]
ranked = sorted(records, key=lambda record: record["score"], reverse=True)

assert ranked[0]["name"] == "Grace"

6.5 Function interview checklist

  • Are inputs, outputs, and failure behavior explicit?
  • Is a mutable default accidentally shared across calls?
  • Does the function rely on hidden global state?
  • Is it doing one coherent job?
  • Would a named function be clearer than a lambda?
  • Can representative and edge cases be tested with simple assertions?