Naming a Piece of Work
Copy the same five lines into three places in a program and you have created three bugs waiting to happen, not three features. Wrap them in a function once, and watch every copy become a single name you can call.
Copy the same five lines into three places in a program and you have created three bugs waiting to happen, not three features. Wrap them in a function once, and watch every copy become a single name you can call.
Naming a piece of work you will repeat
def greet(name): print(f"Hello, {name}!") greet("Ada")greet("Grace")def defines the function once. Every line after it that calls greet(...) reuses that same definition — fix a bug inside greet, and every call to it is fixed, everywhere, in one edit.
The alternative is copying the body wherever you need it. Three copies means three places to remember to update when the greeting changes, and it is exactly the third one, six months from now, that someone forgets.
Writing the docstring so help can find it
A comment above a function explains it to someone reading the source file. A docstring — a string literal as the first line inside the function body — explains it to anyone calling the function, whether or not they ever open the file it lives in.
def greet(name): """Print a friendly greeting to the given name.""" print(f"Hello, {name}!")$ >>> help(greet)$ Help on function greet in module __main__: $ greet(name) Print a friendly greeting to the given name.help() reads that string straight out of the function object and shows it, along with the signature, without you opening the source file at all. An editor's autocomplete pulls from the same place — the docstring you write once is what shows up as documentation everywhere the function is used afterward.
x =
square(x)
return x * x
returns
16
Change x above, and only the input to the machine changes. The function's own definition — the middle box — never moves. That is what naming a piece of work buys you: change the input, run the same code, get the answer for that input.
Parameters are just names local to the call
name inside greet is a name that exists only while that particular call is running. Each call gets its own — calling greet("Ada") and greet("Grace") back to back does not leave name holding "Ada" by the time the second call starts.
Arguments can be passed by position, matching the order parameters were declared in, or by name, which reads more clearly the moment a function takes more than one or two of them.
$ >>> def describe(name, age, role):$ ... print(f"{name}, {age}, {role}")$ ...$ >>> describe("Ada", 36, "admin")$ Ada, 36, admin$ >>> describe(role="admin", name="Ada", age=36)$ Ada, 36, adminDefault arguments, and the trap everyone eventually hits
A parameter can carry a default, so a caller who does not have anything special to say for it can just leave it out.
$ >>> def greet(name, greeting="Hello"):$ ... print(f"{greeting}, {name}!")$ ...$ >>> greet("Ada")$ Hello, Ada!$ >>> greet("Grace", "Hi")$ Hi, Grace!That default is evaluated exactly once — when Python reads the def line, not fresh on every call. For an immutable default like the string above, that distinction is invisible. For a mutable one, like a list, it is a common bug beginners write in their first week with functions.
$ >>> def add_item(item, basket=[]):$ ... basket.append(item)$ ... return basket$ ...$ >>> add_item("apple")$ ['apple']$ >>> add_item("banana")$ ['apple', 'banana']The second call did not start from an empty basket, and nothing about the call site hints why. The fix is to default to None, then create a fresh list inside the function body only when nothing was passed in:
$ >>> def add_item(item, basket=None):$ ... if basket is None:$ ... basket = []$ ... basket.append(item)$ ... return basket$ ...$ >>> add_item("apple")$ ['apple']$ >>> add_item("banana")$ ['banana']Packing extra arguments with *args and **kwargs
Sometimes you do not know how many arguments a function should accept until it is called — a logging function might take one message or five. *args collects any number of extra positional arguments into a tuple.
$ >>> def total(*numbers):$ ... return sum(numbers)$ ...$ >>> total(1, 2, 3)$ 6$ >>> total(10, 20, 30, 40)$ 100**kwargs does the same for keyword arguments, collecting anything passed by name into a dictionary instead.
$ >>> def describe(**details):$ ... for key, value in details.items():$ ... print(f"{key}: {value}")$ ...$ >>> describe(name="Ada", role="admin")$ name: Ada$ role: adminThe names args and kwargs are convention, not syntax — the asterisks are what matter. Reach for either only when a function needs to accept an open-ended set of inputs; a fixed, named parameter list is easier to read and easier for an editor to check for you whenever you can get away with one.
Return sends a value back; print only ever shows you one
print writes text to the screen and hands nothing back to the rest of the program. return sends a value back to wherever the function was called from, so it can be stored, compared, or passed to something else.
$ >>> def add(a, b):$ ... return a + b$ ...$ >>> result = add(3, 4)$ >>> result$ 7$ >>> def show_total(a, b):$ ... print(a + b)$ ...$ >>> result = show_total(3, 4)$ 7$ >>> result$ >>> print(result)$ Noneshow_total(3, 4) printed 7 to the screen while it ran, which is easy to mistake for the function having returned it. result holds None, because nothing inside the function ever wrote return. Reaching the end of a function body falls off the end and returns None exactly as if that had been the last line.
return
Hands a value back to the caller. Use it whenever the result needs to be stored, compared, or passed on to something else.
Shows something on screen for a human to read. The function still returns None unless it also has a return.
Key takeaways
- A function names a piece of work once. Every call reuses that same definition.
- A docstring — a string literal as the first line of the body — is what help() and an editor's autocomplete show, so write one for anything you expect someone else to call.
- Parameters are local to each call — one call's values never leak into another's.
- Never default a parameter to a mutable value like a list. It is created once and shared across every call that relies on the default; default to None and build the value inside the function instead.
- print shows you something on screen and returns nothing to the program. return hands a value back, and a function with no return statement returns None.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.