Chapter 4 Functions

4.5 Identifier Scopes

Identifier scope refers to the accessibility of identifiers in different parts of Python code; it is an important concept related to functions. For a simple example of the concept of scopes, consider the accessibility of identifiers from a function body. Statements defined in a function can contain references to identifiers defined outside the function:

In the example above, the identifier math is defined by an import statement outside the function f, but the function body of f contains a direct reference to math. When the function call f(1) is evaluated, the math module object is accessed by the function body successfully, yielding the return value 2! = 2.

Global scope and local scope. Top-level statements in IDLE form the global scope, while statements inside a function body being executed form a local scope. Correspondingly, parameters (e.g. x in the example above) and identifiers defined inside functions (e.g. the variable y in the example above) are called local identifiers. Python allows global identifiers to be accessed by local statements, but not vice versa.

>>> x=1
>>> def f(a):
...     y=a+x
...     return y*y
...
>>> f(1)
4
>>> a
Traceback (most recent call last):
  File "<stdin >", line 1, in <module>
NameError: name ‘a’ is not defined
>>> y
Traceback (most recent call last):
  File "<stdin >", line 1, in <module>
NameError: name ‘y’ is not defined

In the example above, statements in the function body of f have access to the global variable x. However, the local variables a and y in the function f are not accessible by the global scope. This is intuitive, because every time the function f is executed, its local variables are assigned different values. In fact, local scopes are temporary. They are only available during the execution of a function body. Each time a function call is evaluated, a local scope is created, containing the specific arguments and variable values for the call. After the function body is executed, the local scope will become obsolete.

Note also that the global identifier x can be defined after the definition of f; as long as it is defined in the global scope before the first call to f, it can be accessed from the local scope when f is called.

The code above runs successfully even though x is defined after the def statement, because the function definition of f does not invoke execution of the function body, and x is defined before the evaluation of the function call f(1). On the other hand, if x is defined after the call to f, it will not be available in the global scope when the function body of f is executed.

Two local scopes. Identifiers defined in two different local scopes do not clash with each other:

Table 4.1 Example of nested function calls.

main

f

g

a=1

-

-

def f(x): …

-

-

def g(x): …

-

-

a=f(1)

-

-

-

x=1

-

-

y=x+a

-

-

z=g(y)

-

-

-

x=2

-

-

y=x*x*a

-

-

return y

-

return z

-

print (a)

-

-

The dynamic execution sequence of the code above is shown in Table 4.1. Five top-level statements are written in IDLE, and executed in the static order. When the fourth statement, a = f(1), is executed, the right hand side of the = symbol is evaluated, and the control flow enters the function body of f, resulting a new local scope, in which the assignment of the input argument x and the three local statements are executed. When the second local statement, z = g(y), is executed, the right hand side of = is evaluated, and the control flow further enters the function body of g, resulting in a new local scope, in which the assignment of the input argument x and in g the two local statements are executed. During the execution of g’s function body, the local variable x is assigned the value 2, which is different from the local variable x in f-they are independent of each other, each living in its own scope.

After the function body of g is executed, the control flow goes back to f , and the assignment statement z = g(y) finishes with the local identifier z being assigned the value of g(y), 4. At this point, the local scope for the g call is obsolete. After the function body of f is executed, the control flow further goes back to IDLE, and the assignment statement a = f(1) finishes with the global identifier a being assigned the value of f (1), 4. At this point, the local scope for the f is obsolete.

The uniqueness of a function call control flow is that the dynamic execution leaves the caller when the function call expression is evaluated, and does not return to the caller until the callee has finished its own execution sequence. As a result, nested function call result in layered execution sequence such as the one in Table 4.1. This structure is also called a stack.

Statements in two different local scopes cannot access identifiers defined in each other. For example:

>>> a=1
>>> def f(x):
...     y=1
...     z=g(x+1)+y
...     return z
...
>>> def g(x):
...     return x*(y+a)
...
>>> f(1)
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "<stdin>", line 3 in f
   File "<stdin>", line 2, in g
NameError: global name 'y' is not defined

The function call f(1) above leads to an error because the reference to the identifier y in g is unresolvable. There is a y defined in f’s scope, and g is called from f. However, the local scopes of f and g cannot access each other.

Nested local scope. One way of enabling g to access f’s scope is making g an internal function of f, by putting the def statement of g inside f’s function body:

In the example, the function g is itself a local variable in f , because it is defined in the function body of f. The identifier g is in the same scope as the identifiers y and z. When g is internal to f, the scope of g is an *inner scope* of f. The correlation between g’s scope and f’s scope is similar to that between f’s scope and the global scope—when Python cannot find an identifier in the current scope, it searches the *outer scope*. Note that the up-tracing is transitive: if the identifier a can be found in neither g’s scope and its immediate outer scope, f’s scope, Python goes one level further up to find a in the global scope.

When scopes are nested, an identifier in an inner scope overrides identifiers with the same name in an outer scope. For example, when the code above is executed, the value of the local identifier x in g is 2, although the value of the identifier x in f is 1. g’s references to the identifier x are resolved to its local identifier.

Now suppose that the identifier a is also defined in f:

After running the above code, it gives error saying “UnboundLocalError: local variable ‘a’ referenced before assignment on line 6”. The reason is that a in the global scope is not accessible.

To be able to access that a, the global statement should be used. The syntax of the statement is:

global <identifier >

The global statement declares that the identifier in the statement is global; it can be used only inside function definitions. Using the global statement, the code above can be modified as follows:

Check your understanding

© Copyright 2024 GS Ng.

Next Section - 4.6 Modules