PYTHON / FUNCTIONS
Parameters, arguments, and return values
Pass arguments into a function's parameters, return results to the caller, and reason about how names bind to objects across that boundary.
What you will learn
- Distinguish parameters (names in def) from arguments (values at the call site)
- Return a value with return; a function with no return produces None
- Return several results as one tuple and unpack it where you call the function
- Predict when mutating a parameter changes the caller's object and when it does not
Understanding Parameters, arguments, and return values
A parameter is a name written in the def line; an argument is the actual object handed over when the function runs. Calling net_price(19.99, 0.08) creates a fresh local namespace for that call and binds price to 19.99 and tax_rate to 0.08, in positional order. Those names exist only for the duration of the call, which is why two calls to the same function never interfere with each other.
Binding an argument to a parameter is the same operation as an ordinary assignment: the parameter name starts pointing at the object the caller already has, and no copy is made. So if you reassign the parameter inside the body, you only move the local name and the caller sees nothing; if you call a mutating method on the object, the caller sees the change because there is only one object. This single rule replaces the by-value versus by-reference question entirely.
The return statement sends one object back and ends the call immediately, even from inside a loop. If control reaches the end of the body without hitting return, or hits a bare return, the call produces None, which is what you get when you assign the result of a function that only prints. Writing return a, b builds a tuple and returns that one object, and the familiar first, last = ... at the call site is tuple unpacking, not a second return value.
def net_price(price, tax_rate):
tax = price * tax_rate
return round(price + tax, 2)
def report(label, value):
print(label, "=", value)
total = net_price(19.99, 0.08)
print(total)
answer = report("total", total)
print(answer)
Arguments are bound to parameter names like assignments into a private local namespace, and return hands exactly one object back to the caller.
Worked examples
return ends the call, and tuples carry several results
Shows that the first matching return exits immediately and that returning two values really returns one tuple.
def classify(n):
if n < 0:
return "negative"
if n == 0:
return "zero"
return "positive"
def minmax(numbers):
return min(numbers), max(numbers)
print(classify(-4), classify(0), classify(7))
lo, hi = minmax([3, 11, 5])
print(lo, hi)
print(minmax([3, 11, 5]))
Example explained
Line 1classify(-4) hits the first return, so the later if statements never run at all.
Line 2The final return needs no else, because reaching it means every earlier return was skipped.
Line 3return min(numbers), max(numbers) packs both values into a single tuple object.
Line 4lo, hi = ... unpacks that tuple at the call site; printing the call directly shows the tuple as (3, 11).
Rebinding a parameter versus mutating the object
Demonstrates why assigning to a parameter leaves the caller's list alone while append changes it.
def rebind(items):
items = items + [99]
return items
def mutate(items):
items.append(99)
return items
original = [1, 2]
copy_result = rebind(original)
print(original, copy_result)
same = mutate(original)
print(original, same, same is original)
Example explained
Line 1items = items + [99] builds a new list and points the local name at it, leaving original untouched.
Line 2items.append(99) sends a message to the very object the caller passed, so original grows.
Line 3mutate returns the same object it received, which is why same is original is True.
Line 4Nothing was copied on the way in; only the rebinding in rebind created a second list.
Argument count is checked at call time
Shows the TypeError raised when the arguments supplied do not fill every parameter.
def area(width, height):
return width * height
print(area(3, 4))
try:
area(3)
except TypeError as e:
print(e)
Example explained
Line 1area(3, 4) binds width to 3 and height to 4 purely by position.
Line 2area(3) fails before the body runs, because height has no value to bind to.
Line 3The error names the missing parameter, which is why descriptive parameter names pay off.
Line 4An extra argument raises the mirror-image error about too many positional arguments.
Important notes
There is no way for a function to return nothing; a bare return and falling off the end both produce None.
Any statement after a return in the same block is unreachable and will never execute.
Common mistakes
Printing the result instead of returning it: the call still works, but the caller's variable is None and later arithmetic fails with a TypeError about NoneType.
Putting return inside a loop when you meant to collect values: the function exits on the first iteration and returns only one result.
Assuming a list or dict argument is a private copy and calling sort() or append() on it: the caller's data is silently rearranged or grown.
Try it yourself
Change, predict, then run
Write initials(full_name) that returns the first letter of the first word and of the last word as two values, then call it with "Ada Byron Lovelace" and unpack the result into two names before printing them.
Open the Python workspaceCheck your understanding
A function is called as f(scores) where scores is a list. Its first statement is scores = scores[:], and its second is scores.sort(). Why is the caller's list left unsorted?
- The assignment rebinds the local parameter name to a new list, so sort() reorders only that copy
- Lists are immutable, so sort() cannot change them in place
- Python always passes a copy of every argument into a function
- sort() builds and returns a new list and never touches the object it is called on
Show answer
scores[:] creates a second list and the assignment points the local parameter name at it, so the in-place sort touches the copy and the caller's object is untouched. Option 3 is tempting because the result looks like copy semantics, but nothing is copied at call time: remove the scores = scores[:] line and the caller's list would be sorted.