Functions · Lesson 10 of 18 · 20 min

Functions and scope

Parameters, return, LEGB, and the default-argument trap.

  • Define parameters: positional, keyword, defaults, *args, **kwargs
  • Trace LEGB name lookup
  • Avoid mutable default arguments

A function is an object you call

`def` is an assignment. It creates a function object and binds it to a name. That object carries the code, default values, and a reference to the defining environment. Because functions are objects, you can pass them, store them in lists, and return them.

Parameters have a strict order: positional-only (`/` in the signature), ordinary positional-or-keyword, variadic `*args`, keyword-only (after `*`), and `**kwargs`. You will not need all of these every day, but you will read them in the standard library.

The parameter catalog
def connect(host, port=5432, /, *, timeout=5, **opts):
    return {"host": host, "port": port, "timeout": timeout, **opts}

print(connect("localhost"))
print(connect("localhost", 6432, timeout=1, ssl=True))

def gather(a, *rest):
    return a, rest

print(gather(1, 2, 3))

LEGB

Name lookup walks Local, Enclosing, Global, Built-in. Assignment without `global` or `nonlocal` always creates a local. That is why `count = count + 1` inside a function errors if you meant the module-level count: Python sees the assignment and treats `count` as local for the whole function.

`global name` rebinds a module name. `nonlocal name` rebinds a name in the nearest enclosing function. Use both rarely. Returning values and passing arguments is clearer than mutating outer state.

Locals, enclosing, and the assignment rule
x = "global"

def outer():
    x = "enclosing"
    def inner():
        return x
    return inner()

print(outer())

def broken():
    print(x)
    # assigning x here would make the print above fail:
    # Python would treat x as local for the whole function.

broken()
The default you will never write again
def add_bad(item, bucket=[]):
    bucket.append(item)
    return bucket

print(add_bad(1))
print(add_bad(2))

def add_good(item, bucket=None):
    if bucket is None:
        bucket = []
    bucket.append(item)
    return bucket

print(add_good(1), add_good(2))

Check yourself

Quiz

  1. 1.After `def f(a, b=1):`, a valid call is:

  2. 2.Why does `def f(xs=[]): xs.append(1); return xs` grow across calls?

  3. 3.LEGB stands for:

Practice

Exercise

Write `clamp(n, lo=0, hi=100)` that returns n limited to [lo, hi]. Write `first(*values, default=None)` that returns the first argument, or default if none were passed.

Tutor

Ask a question about this lesson. Answers are generated on demand and capped so the session stays focused.