ProgrammingPython Developer

What is the 'LEGB rule' in Python? How does it work when searching for a variable name? Provide examples of situations where a misunderstanding of this rule leads to non-trivial bugs.

Pass interviews with Hintsage AI assistant

Answer.

LEGB rule is the rule for name resolution in Python. The abbreviation stands for:

  • Local — local namespace (inside a function);
  • Enclosing — namespaces of all enclosing functions;
  • Global — module scope (global variables);
  • Builtin — built-in names in Python (len, list, etc.).

When Python encounters a variable, it first looks locally, then in enclosing scopes, then globally, and finally in the namespace of built-in objects:

def outer(): x = 'enclosing' def inner(): x = 'local' print(x) inner() outer() # 'local'

If the assignment of x is removed inside inner(), Python will take x from the enclosing scope (outer). If there is no variable there, it searches globally, then in built-ins.


Trick question.

How would the function's behavior change if you forget to declare nonlocal or global when modifying a variable from an outer scope?

Answer: If you try to modify an outer scope variable inside an inner function without declaring it nonlocal (or global — if the variable is at the module level), Python will create a new local variable, not changing the outer one.

Example:

x = 10 def foo(): x = 20 # This is a new local x, the global one is not changed foo() print(x) # 10

To change the global x:

def foo(): global x x = 20

For variables in the enclosing function — use nonlocal.


Examples of real bugs due to ignorance of subtleties of the topic.


Story 1

In an educational project, nested functions were used to count the number of calls. The developer wrote:

def counter(): count = 0 def inc(): count += 1 return count return inc

But it raised an UnboundLocalError because count was considered a new variable inside inc() (a local count was created). The solution: declare count as nonlocal.


Story 2

In a web application, they wanted to modify a globally defined cache in a handler:

cache = {} def add_to_cache(key, value): cache[key] = value

However, if they tried to completely overwrite cache inside the function, for example cache = {key: value}, this would create a new local cache unrelated to the global one due to the absence of global cache.


Story 3

In a large ETL system, a bug occurred: programmers left the variable result without explicit initialization inside a function, expecting it to take the value from the outer scope. However, after several iterations, the result began to diverge because inside there was an operation result += value rather than result = result + value (without nonlocal/global) — and result was re-initialized on each call.