Python: LOAD_FAST vs. LOAD_DEREF with inplace addition

Are you making it too hard? var cannot be local because it's being dereferenced before assignment, and it cannot be non-local (unless declared global or nonlocal) because it's being assigned to.

The language is designed this way so that (a) you don't accidentally stomp on global variables: Assigning to a variable makes it local unless you explicitly declare it global or nonlocal. And (b) you can easily use the values of variables in outer scopes. If you dereference a name you haven't defined locally, it looks for it in enclosing scopes.

Your code must dereference the variable before it can increment it, so the rules of the language make the variable both local and non-local--a contradiction. The result: Your code will only run if you declare var to be nonlocal.


Python has a very simple rule that assigns each name in a scope to exactly one category: local, enclosing, or global/builtin.

(CPython, of course, implements that rule by using FAST locals, DEREF closure cells, and NAME or GLOBAL lookups.)


Your changed rule does make sense for your dead-simple case, but it's easy to come up with cases where it would be ambiguous (at least for a human reader, if not for the compiler). For example:

def outer():
    var = 1

    def inner():
        if spam:
            var = 1
        var += 1
        return var

    return inner

Does that var += 1 do a LOAD_DEREF or LOAD_FAST? We can't know until we know the value of spam at runtime. Which means we can't compile the function body.


Even if you could come up with a more complicated rule that makes sense, there's virtue inherent in the rule being simple. Besides being easy to implement (and therefore easy to debug, optimize, etc.), it's easy for someone to understand. When you get an UnboundLocalError, any intermediate-level Python programmer knows how to work through the rule in his head and figure out what went wrong.


Meanwhile, notice that when this comes up in real-life code, there are very easy ways to work around it explicitly. For example:

def inner():
    lvar = var + 1
    return lvar

You wanted to load the closure variable, and assign to a local variable. There's no reason they need to have the same name. In fact, using the same name is misleading, even with your new rule—it implies to the reader that you're modifying the closure variable, when you really aren't. So just give them different names, and the problem goes away.

And that still works with the nonlocal assignment:

def inner():
    nonlocal var
    if spam:
        var = 1
    lvar = var + 1
    return lvar

Or, of course, there are tricks like using a parameter default value to create a local that starts off with a copy of the closure variable:

def inner(var=var):
    var += 1
    return var