# Python Programming/Scoping

### Variables

Variables in Python are automatically declared by assignment. Variables
are always references to objects, and are never typed. Variables exist
only in the current scope or global scope. When they go out of scope,
the variables are destroyed, but the objects to which they refer are not
(unless the number of references to the object drops to zero).

Scope is delineated by function and class blocks. Both functions and
their scopes can be nested. So therefore

``` python
def foo():
    def bar():
        x = 5 # x is now in scope
        return x + y # y is defined in the enclosing scope later
    y = 10
    return bar() # now that y is defined, bar's scope includes y
```

Now when this code is tested,

``` python
>>> foo()
15
>>> bar()
Traceback (most recent call last):
  File "<pyshell#26>", line 1, in -toplevel-
    bar()
NameError: name 'bar' is not defined
```

The name \'bar\' is not found because a higher scope does not have
access to the names lower in the hierarchy.

It is a common pitfall to fail to lookup an attribute (such as a method)
of an object (such as a container) referenced by a variable before the
variable is assigned the object. In its most common form:

``` python
>>> for x in range(10):
         y.append(x) # append is an attribute of lists

Traceback (most recent call last):
  File "<pyshell#46>", line 2, in -toplevel-
    y.append(x)
NameError: name 'y' is not defined
```

Here, to correct this problem, one must add y = \[\] before the for
loop.

A loop does not create its own scope:

``` python
for x in [1, 2, 3]:
  inner = x
print(inner) # 3 rather than an error
```

### Keyword global

Global variables of a Python module are read-accessible from functions
in that module. In fact, if they are mutable, they can be also modified
via method call. However, they cannot modified via a plain assignment
unless declared *global* in the function.

An example to clarify:

``` python
count1 = 1
count2 = 1
list1 = []
list2 = []

def test1():
  print(count1)   # Read access is unproblematic, referring to the global

def test2():
  try:
    print(count1) # This print would be unproblematic, but it throws an error ...
    count1 += 1   # ... since count1 += 1 causes count1 to be local.
  except UnboundLocalError as error:
    print("Error caught:", error)

def test3():
  list1 = [2]     # No outside effect; this rebinds list1 to be a local variable

def test4():
  global count2, list2
  print(count1)   # Read access is unproblematic, referring to the global
  count2 += 1     # We can modify count2 via assignment
  list1.append(1) # Impacts the global list1 even without global declaration
  list2 = [2]     # Impacts the global list2

test1()
test2()
test3()
test4()

print("count1:", count1) # 1
print("count2:", count2) # 2
print("list1:", list1)   # [1]
print("list2:", list2)   # [2]
```

Links:

-   6.13. The global
    statement,
    docs.python.org
-   What are the rules for local and global variables in
    Python?
    in Programming FAQ, docs.python.org

### Keyword nonlocal

Keyword nonlocal, available since Python 3.0, is an analogue of *global*
for nested scopes. It enables a nested function of assign-modify a
variable that is local to the outer function.

An example:

``` python
# Requires Python 3
def outer():
  outerint = 0
  outerint2 = 10
  def inner():
    nonlocal outerint
    outerint = 1 # Impacts outer's outerint only because of the nonlocal declaration
    outerint2 = 1 # No impact
  inner()
  print(outerint)
  print(outerint2)

outer()
```

Simulation of nonlocal in Python 2 via a mutable object:

``` python
def outer():
  outerint = [1]           # Technique 1: Store int in a list
  class outerNL: pass      # Technique 2: Store int in a class
  outerNL.outerint2 = 11
  def inner():
    outerint[0] = 2        # List members can be modified
    outerNL.outerint2 = 12 # Class members can be modified
  inner()
  print(outerint[0])
  print(outerNL.outerint2)

outer()
```

Links:

-   7.13. The nonlocal
    statement,
    docs.python.org

### globals and locals

To find out which variables exist in the global and local scopes, you
can use *locals()* and *globals()* functions, which return dictionaries:

``` python
int1 = 1
def test1():
  int1 = 2
  globals()["int1"] = 3  # Write access seems possible
  print(locals()["int1"])# 2
  
test1()

print(int1)              # 3
```

Write access to locals() dictionary is discouraged by the Python
documentation.

Links:

-   2. Built-in Functions \#
    globals,
    docs.python.org
-   2. Built-in Functions \#
    locals,
    docs.python.org

### External links

-   4. Execution
    model,
    docs.python.org
-   7.13. The nonlocal
    statement,
    docs.python.org
-   PEP 3104 \-- Access to Names in Outer
    Scopes,
    python.org

