What you'll learn
- Tell parameters and arguments apart, and pass arguments by position or by name
- Give a parameter a default value so callers can leave it out
- Return one value, several values, or nothing, and use
Noneon purpose - Understand that a function gets its own local variables that vanish when it returns
Concept
A parameter is a name in the function's definition; an argument is the value you pass when you call it. In def line(name, price): the parameters are name and price; in line("coffee", 3.5) the arguments are "coffee" and 3.5. When the call happens, each argument is assigned to its parameter, and the body runs with those names bound.

You can pass arguments two ways. Positional arguments are matched left to right. Keyword arguments name the parameter: line(price=3.5, name="coffee") works in any order and reads well when a function takes several values of the same type. Mixing is allowed as long as the positional ones come first. Use keywords whenever a call would otherwise be a row of unexplained numbers.
A parameter can have a default: def line(name, price, currency="$"). Callers who are happy with dollars pass two arguments; callers who are not pass three. Parameters with defaults must come after the ones without. Defaults are evaluated once, when the function is defined, which is fine for strings and numbers and a trap for lists; you will see that trap in the common mistakes at the end of this lesson.
| Call | name |
price |
currency |
|---|---|---|---|
line("tea", 2) |
"tea" |
2 |
"$" (default) |
line("tea", 2, "€") |
"tea" |
2 |
"€" |
line(price=2, name="tea") |
"tea" |
2 |
"$" |
line("tea", currency="£", price=2) |
"tea" |
2 |
"£" |
return ends the function and sends a value back. To return several things, separate them with commas: return low, high sends a tuple, and the caller can unpack it: low, high = bounds(values). A function without return, or with a bare return, gives back None. Use None deliberately to mean "no result", such as a search that found nothing, and check for it with is None.
Variables created inside a function are local: they exist while the body runs and disappear when it returns. The caller cannot see them, and two calls do not share them. This is what makes functions safe to reuse: nothing leaks out except what you return. Reading an outer variable from inside a function works; assigning to one from inside creates a new local instead, which is the topic of the scope lesson later.

Code
Positional, keyword, and mixed calls of the same function, plus a default in use. Every call produces the same shape of line.
# calls.py
def line(name, price, currency="$"):
return f"{name:<10}{currency}{price:>6.2f}"
print(line("coffee", 3.5))
print(line("tea", 2, "€"))
print(line(price=4, name="juice"))
print(line("water", currency="£", price=1.25))
coffee $ 3.50
tea € 2.00
juice $ 4.00
water £ 1.25Returning more than one value. The function returns a tuple; the caller unpacks it into two names in one line.
# bounds.py
def bounds(values):
"""Return the smallest and largest value as a pair."""
return min(values), max(values)
low, high = bounds([12, 7, 31, 19])
print(low, high)
print(bounds([3, 3]))
7 31
(3, 3)Using None on purpose. A search that finds nothing returns None, and the caller checks for it with is None rather than guessing from a placeholder value.
# find_user.py
def find_user(users, name):
for user in users:
if user["name"] == name:
return user
return None
users = [{"name": "ada", "role": "admin"}, {"name": "linus", "role": "dev"}]
print(find_user(users, "ada"))
print(find_user(users, "grace"))
print(find_user(users, "grace") is None)
{'name': 'ada', 'role': 'admin'}
None
TrueLocals are private to the call. The rate created inside add_tax is gone after the return, and asking for it is a NameError.
# locals.py
def add_tax(amount):
rate = 0.2
return amount * (1 + rate)
total = add_tax(100)
print(total)
print(rate)
120.0
Traceback (most recent call last):
File "locals.py", line 7, in <module>
print(rate)
^^^^
NameError: name 'rate' is not defined. Did you mean: 'range'?Try it yourself
Create converter.py with a function convert(amount, rate=1.0, symbol="$") that returns a formatted string like "$12.50". Call it positionally, with keywords, and relying on both defaults. Then write split_bill(total, people) that returns two values: the share per person and the leftover cents when the total does not divide evenly, and unpack both from a call.
Common mistakes
- Positional after keyword.
line(name="tea", 2)is aSyntaxError; positional arguments always come first. - A mutable default.
def add(item, items=[])shares one list across every call, so items pile up between calls. Useitems=Noneand create the list inside the function. - Forgetting that
returnends the function. Code after areturnon the same path never runs; if you meant to return several things, return a tuple.
What's next
- Parameters are the names in the definition, arguments are the values in the call; pass them by position or by keyword.
- Defaults make parameters optional, must come last, and should never be a mutable value like a list.
- Return several values as a tuple and unpack them;
Nonemeans "no result" and is checked withis None.
You control what goes into a function and what comes out. Next you will handle the cases where you do not know how many arguments are coming, with *args and **kwargs.