1.4Functions, arguments & scope
The essentials in 30 seconds
- Function = one job, one verb name: def compute_vat(price_ht): + return — displaying (print) is not returning.
- Immutable defaults: def f(items=None) then create inside — never def f(items=[]) which survives between calls.
- Local first, global never: data as arguments, result via return — *args/**kwargs for flexibility.
Running mini-case — Nabil refactors his computations in Algiers
Nabil computes the average of scores in four places of his script with the same copy-paste — and when the rule changes (ignore zeros), he forgets two places: one report out of four is wrong. He creates an average(*grades) function that receives everything as parameters and returns the result: a single place to fix. Along the way, he fixes a def add(x, items=[]) that accumulated between calls (None + creation inside) and replaces a print with a return. All four reports become correct at once.
Continuation of cases 1.1-1.3 — Nabil moves from repetition to reuse.
1. One function = one job, one verb name
A function is a named, reusable block: def compute_vat(price_ht): — verb + snake_case name, one responsibility, return the result. Without return, the function returns None: print displays, return passes on — print never replaces return. This is the DRY principle (don't repeat yourself).
Nabil replaces his four copy-pasted averages with average(*grades): a single place to fix when the rule changes, four correct reports.
2. Arguments, defaults and scope
Parameters are declared in the definition, arguments passed at call time; defaults come after required ones (def greet(name, greeting="Hello")). Famous trap: def f(items=[]) — the list survives between calls, write def f(items=None). *args collects values, **kwargs name=value pairs. Scope: a variable created inside the function is local (NameError outside) — pass via arguments, get back via return.
Nabil fixes his mutable default to None, adds average(*grades) for 2 then 5 grades, and sees the NameError of a local variable accessed outside: local vs global, settled.
🔎 Concrete example — 🔎 Concrete example — The default-list trap
The famous bug, like Nabil with his averages.
def ajouter(x, items=None):
items = items if items is not None else []
items.append(x)
return items
print(ajouter(1)) # [1]
print(ajouter(2)) # [2]: no more surprise
def moyenne(*notes):
return sum(notes) / len(notes)
print(moyenne(12, 15, 9))
Reading: the mutable default is created only once — None + creation inside avoids it.
Lesson: *args collects a variable number of arguments; return makes the result reusable.
Section vocabulary
- Function / def
- Named, reusable code block, defined with def name(parameters):. Called via name(arguments). Verb name, one responsibility (DRY).
Simple example: Nabil's single average(*grades), called four times.
What it is not: the same computation copy-pasted in four places.
Reflex: computation repeated twice = function.
- Parameter / argument
- The parameter is the variable declared in the definition; the argument is the real value passed at call time. Defaults come after required ones.
Simple example: The greeting="Hello" of Nabil's greet(name).
What it is not: a mutable [] default shared between calls.
Reflex: immutable defaults (None), never [] or {}.
- return
- Returns a value out of the function and ends its execution. Without return, the function returns None. print displays, return passes on.
Simple example: The average returned by Nabil, stored then displayed.
What it is not: a function that prints with no return, unusable in computations.
Reflex: result to reuse = return, always.
- Scope
- Visibility zone of a variable. A variable created in a function is local: it does not exist outside (NameError).
Simple example: Nabil's local variable, unreachable outside.
What it is not: modifying a global with global instead of a parameter.
Reflex: need data? parameter; need the result? return.
- *args / **kwargs
- Receive a variable number of arguments: *args (tuple of values), **kwargs (dictionary of name=value pairs).
Simple example: Nabil's average(*grades) with 2 then 5 grades.
What it is not: five optional parameters in a row instead of *args.
Reflex: unknown count = *args; named options = **kwargs.
In practice — In practice — Defining and calling functions
- Write a greet(name) function that returns (return) "Hello, {name}!". Call it with different names and display the result.
- Add a default parameter: greet(name, greeting="Hello"). Call it with and without the second argument.
- Write an average(*grades) function accepting a variable number of grades and returning their average. Test with 2, then 5 grades.
- Illustrate scope: create a variable in a function and try to access it outside (NameError). Conclude on local vs global.
Script ready to say — refusing print instead of return (30 s)
"I suggest this function return the result with return rather than display it: that way we can store it, test it and reuse it. print passes nothing on — the call is worth None and the next computation crashes."
Say in code review; show the stored call before/after.
Copy-paste template — healthy functions (return, safe default, *args)
def saluer(nom, politesse="Hello"):
return f"{politesse}, {nom}!"
print(saluer("Nabil"))
print(saluer("Yasmine", politesse="Hi"))
def moyenne(*notes):
return sum(notes) / len(notes)
print(moyenne(12, 15))
print(moyenne(10, 14, 16, 8, 12))
def ajouter(x, items=None):
items = items if items is not None else []
items.append(x)
return items
Averages in Algiers
Like Nabil: a single average(*grades) function — the rule changes once, four correct reports.
External data to use
Pass it as an explicit parameter: the signature documents the need, the function stays testable in isolation.
Default value
The greeting="Hello" default: the simple call covers the common case, the full call the special case.
Variable count
*grades for 2 then 5 grades, **options for named settings: the signature stays short and flexible.
✓ Best practices
- One verb, one job, one return — never print instead.
- Immutable defaults (None, not [] or {}).
- Data as arguments, never as globals.
✗ Common pitfalls
- Printing instead of returning (call worth None).
- Writing a mutable default ([] or {}) that survives between calls.
- Using global to modify outside instead of a parameter.
Key takeaways
- A function (def) groups reusable code: DRY principle — verb name, one responsibility.
- return returns a reusable result; without it, the function returns None (print never replaces return).
- Default arguments, *args and **kwargs offer flexibility — immutable defaults only.
- A variable defined in a function is local: invisible outside — arguments in, return out.
48h challenge — apply tomorrow
Extract a duplicated computation from your script into a function (template above): explicit parameters, return, safe default. Replace a print with a return and test with 2 then 5 arguments. 25 minutes is enough.
- I define functions with parameters, safe default, return and *args.
- I respect local scope — no modified globals.
- I distinguish print (display) from return (pass on).
Frequently asked questions
What is the difference between print() and return in a function?
print() displays on screen but passes nothing to the rest of the program. return returns an exploitable value (storable, computable). A function that "prints" without "return" only displays; to reuse the result you need return — otherwise the call is worth None.
What are *args and **kwargs concretely for?
They make a function flexible about argument counts. *args collects positional values into a tuple (useful for an average of an unknown number of grades); **kwargs collects key=value pairs into a dictionary (useful for named options like sep, title...).
What is the value of calling a function with no return?