Home      |       Contents       |       About

Prev: Arguments       |       Next: Functions as first-class objects

Variable Scope

What is "variable scope"

  • Simply speaking, a "variable scope" in Python is the namespace where a name-object binding is valid and usable.
  • Variable scope becomes vital when practicing modular programming techniques where the programmer must clearly understand how names are bound (or not) to various objects in different parts of the code.
  • Let's see an example:
In [ ]:
def addcon(x,y):
    asum = x+y
    return asum                 

a = 5
b = 10
c = addcon(a,b)                                   
print(c)  
  • When executing the above code two distinct namespaces are developed (left panel in the figure below):
    • global: by global we refer to the namespace of the main program. This contains the names 'addcon', 'a', 'b' and their object bindings. A variable 'is global' ('has global scope') when it belongs to the global namespace and, therefore, it can certainly be used in the main program (and elsewhere as explained further below)
    • local: by local we refer to the namespace created by a function while it is executed. In our example the addcon local namespace includes the names 'x', 'y', 'asum' and their object bindings. A variable 'is local' ('has local scope') when it belongs to a function local namespace and, therefore, it can certainly be used in that function

  • After the addcon() execution is over (right panel in the figure above) its local namespace siezes to exist, but the global namespace is now larger: the name 'c' has been added bound to the object '15' that the function returned

Rules

  • Python applies certain simple rules to define the scope of variables. Let's see them below (remember always that by 'variable' in Python we always mean a name-object binding)

1) Variables belong where the binding is first constructed: if it is constructed in the main program the variable is global. If in a function then the variable is local in that function. 'Constructed' in this context means that a name-object binding was established by an assignment command, such as: x=5, y='spam', alist=[], adict={}, etc.

In [ ]:
def addcon(x,y):
    asum = x+y       # asum is a local var 'constructed' in the function by this assignment
    return asum                 

a = 5                # a and b are global vars 'constructed' in the global namespace of the main program 
b = 10
c = addcon(a,b)      # c is also a global var 'constructed' by this assignment 
print(c)  

2) If a variable is used somewhere but NOT constructed there: then Python interpreter looks up for this variable at the higher levels of a scope hierarchy. If the variable is identified at some level then it is assigned the scope of that level and computation continuous. If it is not identified at any level then a 'NameError' exception is raised and execution is terminated.

3) Scope hierarchy: Python looks for variable scope following the 'LEGB' hierarchy, which means:

  • Local: a variable is sought for first in the local namespace (in the function) where it is used
  • Enclosing: the next level is any enclosing (container) function containing the function where the variable is used
  • Global: the upper level is the global level of the main program
  • Built-ins: finally Python searches for a variable in the built in names that the language reserves (for example, len, sum, class, etc.)

When an module is imported then the namespace is further extended and prefixes are commonly used to correctly identify the imported namespaces.

Examples

In [4]:
def func1():
    
    def func2():
        x = 300           # x is local in func2() because of this assignment
        return x
    
    x = 200               # another x is local in func1() because of this assignment
    a = func2()           
    b = x
    return a, b 

x = 100                   # another x is global; is different from x's in func1() and func2()
print(func1())
print(x)
(300, 200)
100
In [1]:
def func1():
    
    def func2():
        x = 300+a           # a is NOT local in func2() but identified in the higher 'Enclosing' level 
        return x
    
    x = 100+y               # y is NOT local in func1() but identified in the higher 'Global' level
    a = 400           
    b = func2()
    return a, b 

x = 100 
y = 300
print(func1())
print(x)
(400, 700)
100
In [2]:
def func1():
    
    def func2():
        x = 300+a           
        c = 700             # c is local here and NOT available in higher levels of the hierarchy
        return x
    
    x = 200+y               
    a = c                   #--- this returns: 'NameError: name 'c' is not defined'
    b = x
    return a, b 

x = 100 
y = 500
print(func1())
print(x)
print(c)                    #--- this returns: 'NameError: name 'c' is not defined'

4) global and nonlocal: There are two 'declarations' that help override the above rules when necessary:

  • global: when declaring a var as global in a function, it's scope becomes automatically 'global'
  • nonlocal: when declaring a var as nonlocal in an enclosed function, it's scope becomes automatically local in the function above (the enclosing function)

See the examples:

In [3]:
def func1():
    
    def func2():
        nonlocal x        # x is declared as nonlocal; thus, it is identical to 'x' in the higher enclosing func1()
        x = 300           
        return x
    
    x = 200               # because of 'nonlocal' now this 'x' is identical to the 'x' var in enclosed func2()
    a = func2()           
    b = x                 # Now b gets the value '300'. Why?
    return a, b 

x = 100                   # this x is global and different from x in func1() 
print(func1())
print(x)
(300, 300)
100
In [8]:
def func1():
    global x              # x is declared as nonlocal; thus, it is identical to the global 'x' in main program
    
    def func2():
        x = 300           
        return x
    
    x = 200               
    a = func2()           
    b = x                 # Now b gets the value '200'. Why?
    return a, b 

x = 100                   # because of 'global' now this 'x' is identical to the 'x' var in func1() 
print(func1())
print(x)                  # Now x gets the value '200'. Why?
(300, 200)
200

5) Mutable vs. immutable object behavior: when thinking about scope always recall the different behavior of mutable/immutable objects when assigned values in a function:

  • Immutable objects (integerss, strings,..): any assignment is considered as construction and the variable becomes local (a new copy is created). Any global variable of the same name stays intact.
  • Mutable objects (lists, dictionaries,..): assignment of a new value to a member is not considered construction. Any global variable of the same name is affected due to shared object reference.
In [10]:
def myfunc():
    a = 31        # a is immutable; assignment constructs a new local obect in myfunc() 
    b[0] = 'K'    # b is mutable; changing a member value does NOT construct new copy
    return 

a = 10
b = [1,2,3]
myfunc()
print(a, b)       # global a is not affected by myfunc(); list b is affected because of shared object reference 
10 ['K', 2, 3]

. Free learning material
. See full copyright and disclaimer notice