Message 402992 - Python tracker (original) (raw)

Aaron: Your understanding of how LEGB works in Python is a little off.

Locals are locals for the entire scope of the function, bound or unbound; deleting them means they hold nothing (they're unbound) but del can't actually stop them from being locals. The choice of whether to look something up in the L, E or GB portions of LEGB scoping rules is a static choice made when the function is defined, and is solely about whether they are assigned to anywhere in the function (without an explicit nonlocal/global statement to prevent them becoming locals as a result).

Your second example can be made to fail just by adding a line after the print:

def doSomething(): print(x) x = 1

and it fails for the same reason:

def doSomething(): x = 10 del x print(x)

fails; a local is a local from entry to exit in a function. Failure to assign to it for a while doesn't change that; it's a local because you assigned to it at least once, along at least one code path. del-ing it after assigning doesn't change that, because del doesn't get rid of locals, it just empties them. Imagine how complex the LOAD_FAST instruction would get if it needed to handle not just loading a local, but when the local wasn't bound, had to choose dynamically between:

  1. Raising UnboundLocalError (if the value is local, but was never assigned)
  2. Returning a closure scoped variable (if the value was local, but got del-ed, and a closure scope exists)
  3. Raising NameError (if the closure scope variable exists, but was never assigned)
  4. Returning a global/builtin variable (if there was no closure scope variable or the closure scope variable was created, but explicitly del-ed)
  5. Raising NameError (if no closure, global or builtin name exists)

That's starting to stretch the definition of "fast" in LOAD_FAST. :-)