Scopes

Dynamic vs Lexical Scope

An identifier such as variable name is "in scope" if it is bound to a value.

Identifiers exist in "namespaces". Python namespaces are like dictionaries in that identifiers can be added or removed at execution time. This is called "dynamic scope".

This differs from languages such as C where the scope of an identifier is defined at compile time and is determined by the location of the identifier's declaration in the program. This is called "lexical scope".

In [1]:
try:
    print(a)
except Exception as e:
    print(e)
a=1
print(a)
del(a)
print(a)
name 'a' is not defined
1
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-90a8a712f01d> in <module>
      6 print(a)
      7 del(a)
----> 8 print(a)

NameError: name 'a' is not defined

Local Variables

In a Python function, we can create a local variable simply by assigning to it. The scope of variables assigned to within a function is limited to that function. Their scope is "local". This allows you to freely re-use names without worrying about conflicts with variables defined in other functions.

Variables that are not assigned to in a function refer to variables in an enclosing scope (global scope or an enclosing function's scope).

Variables assigned to outside any function are in the __main__ namespace and are "global". It is possible to refer to a global variable by using the global statement.

In the following example there are two scopes: the global one where x and y are bound to an integer with value 1 and the scope of the function f where a new variable x is created in the function's local namespace because it is assigned to. The global y statement binds y to the value in the enclosing scope. z exists only the function's namespace.

In this example the global variable x is "shadowed" by the local variable x.

In [2]:
x=1
y=1

def f():
    global y
    x=2
    y=2
    z=2
    print(x,y,z)

f()
print(x,y)
import __main__
'x' in dir(__main__), 'z' in dir(__main__)
2 2 2
1 2
Out[2]:
(True, False)

Module Namespaces

Importing a module also creates a namespace. Variables and functions in the file that is imported are placed in a namespace that has the same name as the file. For example, when we import from the file math.py, the function cos and the variable pi are placed in the math namespace:

In [3]:
import math
math.myfunction = 1
print(dir(math),math.myfunction)
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'myfunction', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc'] 1
In [8]:
from math import *
math=0
print(dir(__main__),'math' in dir(__main__))
['In', 'Out', '_', '_2', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__main__', '__name__', '__package__', '__spec__', '_dh', '_i', '_i1', '_i2', '_i3', '_i4', '_i5', '_i6', '_i7', '_i8', '_ih', '_ii', '_iii', '_oh', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exit', 'exp', 'expm1', 'f', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'get_ipython', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'math', 'modf', 'myfunction', 'nan', 'pi', 'pow', 'quit', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc', 'x', 'y'] True

The global namespace is in a module called __main__. Built-in identifiers exist the __builtin__ namespace in the global namespace:

In [1]:
import __main__
print(dir(__main__))
print(dir(__main__.__builtin__))
['In', 'Out', '_', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__main__', '__name__', '__package__', '__spec__', '_dh', '_i', '_i1', '_ih', '_ii', '_iii', '_oh', 'exit', 'get_ipython', 'quit']
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__IPYTHON__', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'display', 'divmod', 'enumerate', 'eval', 'exec', 'filter', 'float', 'format', 'frozenset', 'get_ipython', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

Object Scope

Each instance of an object also has its own scope.

In [9]:
class newclass():
    def __init__(self,x):
        self.x=x
    
c1=newclass(1)
c2=newclass(2)
x=3

print(c1.x,c2.x,x)
1 2 3

Scope Resolution

A name can exists in multiple scopes. The priority is:

  1. local function scope
  2. object scope
  3. global scope
In [12]:
x=3

class myclass():
    # class variable shared by all instances of myclass:
    x=2
    def __init__(self,x):
        # instance variable (attribute) unique to an instance
        self.x=x
    def f1(self):
        # which x does it change?
        x=1
        return x
    def f2(self):
        return self.x
    def f3(self):
        # how is this different than f1?
        return x
    


c=myclass(4)

# scopes: function, object, and global:
print(c.f1(), c.f2(), c.f3())

# global scope (in main namespace, unchanged)
print(x)

# module scope

import math
math.x=4
print(math.x)
1 4 3
3
4

Closures

Each function has its own local namespace. This namespace is bound to the function object. Each time we create a new function we create a new namespace.

An alternative to creating data objects is to create functions that hold their own data. Functions that contain data (state) are called "closures".

In this example we create a function object that returns the smallest value it has been called with since it was created.

We could accomplish the same thing by defining a new type of object with appropriate methods, but a closure has less overhead.

In [13]:
# a function that returns a function:
def newmin():
    m=None
    def f(x):
        # 'nonlocal' binds m to the enclosing scope
        # instead of the global scope
        nonlocal m  
        if m == None or x<m:
            m=x
        return m
    return f
    
min1=newmin()
print(min1(15),min1(10),min1(20))

min2=newmin()
print(min2(10),min2(5),min2(15))

print(min1(100),min2(100))
15 10 10
10 5 5
10 5
In [ ]: