Part 3 of 6 · Chapter 4 of 4

Where a Name Actually Lives

A variable created inside a function looks like it should be visible everywhere once the function has run. Try to read it from outside, and find out Python disagrees, on purpose.

Intermediate10 min read

Worth reading first: Naming a Piece of Work


A variable created inside a function looks like it should be visible everywhere once the function has run. Try to read it from outside, and find out Python disagrees, on purpose.

A name only exists where it was created

Terminal
$ >>> def set_total():$ ...     total = 100$ ...$ >>> set_total()$ >>> total$ NameError: name 'total' is not defined

totalwas assigned, and the function ran without error. It still does not exist outside the function, because a name created inside a function belongs to that function's own local scope, and that scope disappears the moment the function returns.

Reading an outer name is allowed; changing it is not, by default

A function can read a name defined outside it without any special syntax. The moment it tries to assign to that name, Python assumes you meant to create a new local variable instead — even if a variable with that name already exists outside.

Default arguments are evaluated once, not every call

A default value in a function signature is evaluated exactly once, when the function is defined — not fresh on every call. For a mutable default like a list, that single shared object gets reused across every call that relies on the default.

Terminal
$ >>> 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. Both calls share the exact same list, created once when Python read the def line. The fix is to default to None and create a fresh list inside the function body when nothing was passed.

Key takeaways

  • A name created inside a function belongs to that function and disappears when it returns.
  • Assigning to a name anywhere in a function makes Python treat it as local throughout the whole function, even before the assignment line runs.
  • Use the global keyword to assign to an outer name from inside a function — but prefer passing values in and returning them instead.
  • A mutable default argument, like basket=[], is created once and shared across every call that uses the default.