1.4 · Functions, arguments & scope

Level 1 · Language basics

1.4Functions, arguments & scope

Goal: define functions with def, handle arguments, default values, *args/**kwargs, return and variable scope.
Estimated time: 14 min · self-paced reading · If you only have 5 min: read “The essentials in 30s” + the diagram.

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.

Common misinterpretation: "print inside the function is enough" or "a [] default is handy". No. print displays on screen but passes nothing on: without return, the call is worth None and the result can be neither stored nor computed. And the mutable default is created only once: def f(items=[]) accumulates between calls — None + creation inside, always.
Healthy functions One job, safe defaults, local scope One job (verb) def +return, not printDefaults (immutable) None,not []; *args/**kwargsScope (local) Argumentsin, return out
One job, safe defaults, local scope
Text version of the diagram (SEO / screen readers / translation): 1) Define: Nabil writes def average(*grades) once instead of four copy-pastes — verb name, one responsibility, return. 2) Secure: immutable defaults (None + creation inside), *args for variable counts, **kwargs for options. 3) Isolate: local variables, everything enters via arguments, everything exits via return — no globals. Lesson: one function = explicit interface, isolated space, returned result.

🔎 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

  1. Write a greet(name) function that returns (return) "Hello, {name}!". Call it with different names and display the result.
  2. Add a default parameter: greet(name, greeting="Hello"). Call it with and without the second argument.
  3. Write an average(*grades) function accepting a variable number of grades and returning their average. Test with 2, then 5 grades.
  4. Illustrate scope: create a variable in a function and try to access it outside (NameError). Conclude on local vs global.
You create reusable functions with arguments, default values and return, and you understand the local scope of variables.

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.

Section exit checklist
  • 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).
Self-assessment 1-5: do my functions receive everything as parameters?Self-assessment 1-5: does each reused result exit via return?

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...).

Check your understanding

What is the value of calling a function with no return?

Additional resources (optional, to go further)

Level 1 examFresh, harder questions to test what you've learned