Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scoping rules in python

Consider the following python snippet (I am running Python 3)

name = "Sammy" 

def greet():
    name = 'johny'
    def hello():
        print('hello ' + name) # gets 'name' from the enclosing 'greet'
    hello()

greet()

This produces the output hello johny as expected

However,

x = 50

def func1():
    x = 20
    def func2():
        print("x is ", x) # Generates error here
        x = 2
        print("Changed the local x to ",x)
    func2()

func1()
print("x is still ",x)

generates an UnboundLocalError: local variable 'x' referenced before assignment.

Why does the first snippet work, whereas the second doesn't?

like image 982
Aswin P J Avatar asked May 10 '18 10:05

Aswin P J


2 Answers

The error is actually caused (indirectly) by the following line, i.e. x = 2. Try commenting out that line and you'll see that the function works.

The fact that there is an assignment to a variable named x makes x local to the function at compile time, however, at execution time the first reference to x fails because, at the time that the print() statement is executed, it does not exist yet in the current scope.

Correct it by using nonlocal in func2():

def func2():
    nonlocal x
    print("x is ", x)
    x = 2
    print("Changed the local x to ",x)

The reason that the first function (greet()) works is because it's OK to read variables in an outer scope, however, you can not assign to them unless you specify that the variable exists in an outer scope (with nonlocal or global).

You can assign to a variable of the same name, however, that would create a new local variable, not the variable in the outer scope. So this also works:

def func1():
    x = 20
    def func2():
        x = 2
        print('Inner x is', x)
    func2()
    print('Outer x is', x)

Here x is assigned to before being referenced. This creates a new variable named x in the scope of function func2() which shadows the x defined in func1().

like image 72
mhawke Avatar answered Sep 26 '22 07:09

mhawke


In a given scope, you can only reference a variable's name from one given scope. Your variable cannot be global at some point and local later on or vice versa.

For that reason, if x is ever to be declared in a scope, Python will assume that you are refering to the local variable everywhere in that scope, unless you explicitly state otherwise.

This is why you first function, greet, works. The variable name is unambiguously coming from the closure. Although, in func2 the variable x is used in the scope and thus you cannot reference the x from the closure unless explicitly stating otherwise with nonlocal.

The following errors might enlighten us on this.

A variable cannot become global after use

def func1():
    x = 20
    def func2():
        print("x is ", x)
        global x
        print("Changed the local x to ",x)
    func2()

This raises a SyntaxError: name 'x' is used prior to global declaration. This means that the closure's x cannot be used and then the global one.

A global variable cannot become local

Here is another case using global at the top of func2.

def func1():
    x = 20
    def func2():
        global x
        print("x is ", x)
        x = 2
        print("Changed the local x to ",x)
    func2()

This code was exectued without error, but notice that the assignment to x updated the global variable, it did not make x become local again.

like image 39
Olivier Melançon Avatar answered Sep 22 '22 07:09

Olivier Melançon