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:
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
- Its value.
- Value is the contents of the variable. Scope concerns where the variable is "known".
- The range of statements in the code where a variable can be accessed.
- Yes, the part of Python code where variable's content can be accessed.
- Its name.
- The name of a variable is just an identifier or alias. Scope concerns where the variable is "known".
4.5Q-1: What is a variable’s scope?
- A temporary variable that is only used inside a function
- Yes, a local variable is a temporary variable that is only known (only exists) in the function it is defined in.
- The same as a parameter
- While parameters may be considered local variables, functions may also define and use additional local variables.
- Another name for any variable
- Variables that are used outside a function are not local, but rather global variables.
4.5Q-2: What is a local variable?
- Yes, and there is no reason not to.
- While there is no problem as far as Python is concerned, it is generally considered bad style because of the potential for the programmer to get confused.
- Yes, but it is considered bad form.
- it is generally considered bad style because of the potential for the programmer to get confused. If you must use global variables (also generally bad form) make sure they have unique names.
- No, it will cause an error.
- Python manages global and local scope separately and has clear rules for how to handle variables with the same name in different scopes, so this will not cause a Python error.
4.5Q-3: Can you use the same name for a local variable as a global variable?
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
- Although Python typically processes lines in order from top to bottom, function definitions and calls are an exception to this rule.
- 1, 2, 3, 5, 6, 7, 9, 10, 11
- Although Python typically processes lines in order from top to bottom, function definitions and calls are an exception to this rule. Although this order skips blank lines, it still lists the lines of code in order.
- 9, 10, 11, 1, 2, 3, 5, 6, 7
- This is close, in that Python will not execute the functions until after they are called, but there are two problems here. First, Python does not know which lines are function definitions until it processes them, so it must at least process the function headers before skipping over the functions. Section, notice that line 10 involves a function call. Python must execute the function square before moving on to line 11.
- 9, 10, 5, 6, 7, 1, 2, 3, 11
- This is close, in that Python will not execute the functions until after they are called, but there is one problem here. Python does not know which lines are function definitions until it processes them, so it must at least process the function headers before skipping over the functions.
- 1, 5, 9, 10, 6, 2, 3, 7, 11
- Python starts at line 1, notices that it is a function definition and skips over all of the lines in the function definition until it finds a line that it no longer included in the function (line 5). It then notices line 5 is also a function definition and again skips over the function body to line 9. On line 10 it notices it has a function to execute, so it goes back and executes the body of that function. Notice that that function includes another function call. Finally, it will return to line 11 after the function square is complete.
4.5Q-4: Consider the following Python code. Note that line numbers are included on the left.
1 2 3 4 5 6 7 8 9 10 11 | def pow(b, p):
y = b ** p
return y
def square(x):
a = pow(x, 2)
return a
n = 5
result = square(n)
print(result)
|
Which of the following best reflects the order in which these lines of code are processed in Python?
- 25
- The function square returns the square of its input (via a call to pow)
- 5
- What is printed is the output of the square function. 5 is the input to the square function.
- 125
- Notice that pow is called from within square with a base (b) of 5 and a power (p) of two.
- 32
- Notice that pow is called from within square with a base (b) of 5 and a power (p) of two.
4.5Q-5: Consider the following Python code. Note that line numbers are included on the left.
1 2 3 4 5 6 7 8 9 10 11 | def pow(b, p):
y = b ** p
return y
def square(x):
a = pow(x, 2)
return a
n = 5
result = square(n)
print(result)
|
What does this function print?
© Copyright 2024 GS Ng.