Skip to content
Module 05 of 1250 min readBeginner

Functions and scope

Defining, calling, default arguments, *args and **kwargs, and the LEGB scope rule.

42%

Listen along

Read “Functions and scope” aloud

Plays in your browser using on-device text-to-speech — nothing leaves the page.

Learning objectives

By the end of this module, you should be able to:

  • 01Define functions with positional, default, and keyword-only arguments
  • 02Use *args and **kwargs to write flexible functions and to unpack arguments at call sites
  • 03Apply the LEGB scope rule (Local, Enclosing, Global, Built-in) to understand name resolution
  • 04Avoid the mutable-default-argument trap

Functions are how you package up reusable logic. Python's function syntax is clean, supports default arguments, variable-length arguments, and (since 3.5) type hints. Knowing the LEGB scope rule is what separates competent Python from confused Python.

Defining and calling

python
def compound(principal, rate, years):
return principal * (1 + rate) ** years
compound(1000, 0.10, 5) # 1610.51

Default arguments

Default arguments let you call a function with fewer arguments. Defaults are evaluated once at function definition time — using a mutable default (like an empty list) is a notorious bug.

python
def compound(principal, rate, years=10):
return principal * (1 + rate) ** years
compound(1000, 0.10) # uses years=10
compound(1000, 0.10, 5) # overrides to 5

The mutable-default trap

def add_rate(rate, history=[]): history.append(rate); return history. The default list is shared across all calls — every call mutates the same list. Fix: use None as the default and create a fresh list inside the function.

Keyword arguments

Named arguments make calls self-documenting. The forced-keyword convention (using * to mark the boundary) is increasingly common in modern Python.

python
def compound(principal, *, rate, years): # rate and years must be passed by keyword
return principal * (1 + rate) ** years
compound(1000, rate=0.10, years=5)

*args and **kwargs

*args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dict. Use them when you don't know in advance how many arguments will be passed.

python
def total(*amounts):
return sum(amounts)
total(100, 200, 300) # 600
total(*[10, 20, 30]) # 60 (unpacking a list)

Scope: LEGB

Python looks up names in this order: Local (the current function), Enclosing (any outer function), Global (module level), Built-in. A name not bound locally and not declared global will fall through to enclosing/global scope. This is usually intuitive; the surprises happen when you assign to a global name from inside a function (you must declare 'global x' first).

Exercise

Write a function that takes a list of rates and returns their average.

Key takeaways

  • Default arguments are evaluated once at definition — never use [] or {} as a default; use None and create inside
  • *args collects positional args into a tuple; **kwargs collects keyword args into a dict
  • Forced-keyword arguments (def f(*, x, y)) make calls self-documenting at call sites
  • Scope is LEGB: Local → Enclosing → Global → Built-in. Assigning to a global from inside a function requires `global`

Further reading

  1. 01
  2. 02

    Fluent Python, Chapter 7: Functions as First-Class Objects

    Luciano Ramalho · O'Reilly · 2022

  3. 03

    Effective Python, Items 19-26

    Brett Slatkin · Addison-Wesley · 2019

Loading progress…
LeadAfrikPublic Economics Hub