CHAPTER 7

Namespaces

7.1 Encapsulation in Functions

7.2 Global versus Local Namespaces

7.3 Exceptional Control Flow

7.4 Modules as Namespaces

7.5 Classes as Namespaces

Chapter Summary

Solutions to Practice Problems

Exercises

Problems

THIS CHAPTER presents namespaces as a fundamental construct for managing program complexity. As computer programs increase in complexity, it becomes necessary to adopt a modular approach and develop them using several smaller components that are developed, tested, and debugged individually. These components—whether they are functions, modules, or classes—must work together as a program but they also should not interfere, in unintended ways, with each other.

Modularity and “noninterference” (usually called encapsulation) are made possible thanks to the fact that each component has its own namespace. Namespaces organize the naming scheme in functions, modules, and classes so that names defined inside a component are not visible to other components. Namespaces play a key role in the execution of function calls and the normal control flow of a program. We contrast this with the exceptional control flow that is caused by a raised exception. We introduce exception handling as a way to control this control flow.

This chapter covers concepts and techniques that fundamentally deal with program design. We apply them in Chapter 8 to create new classes and in Chapter 10 to understand how recursive functions execute.

7.1 Encapsulation in Functions

In Chapter 3, we introduced functions as wrappers that package a fragment of code. To recall the reasons for wrapping code into functions—and then using those functions—we look back at the turtle graphics jump() function we developed in Chapter 3:

Module: turtlefunctions.py
1  def jump(t, x, y):
2      ‘makes turtle t jump to coordinates (x,y)’
3      t.penup()
4      t.goto(x,y)
5      t.pendown()

The function jump() provides a succinct way to make the turtle object t move to a new location without leaving a trace. In Chapter 3, we used jump() multiple times in the function emoticon() that draws a smiley face:

Module: turtlefunctions.py
1  def emoticon(t,x,y):
2      ‘directs turtle t to draw a smiley face with chin at (x,y)’
3      t.pensize(3)           # set turtle heading and pen size
4      t.setheading(0)
5      jump(t,x,y)            # move to (x,y) and draw head
6      t.circle(100)
7      jump(t,x+35,y+120)     # move and draw right eye
8      t.dot(25)
9      jump(t,x-35,y+120)     # move and draw left eye
10     t.dot(25)
11     jump(t,x-60.62,y+65)   # move and draw smile 12 t.setheading(-60)
13     t.circle(70,120)       # 120 degree section of a circle

The functions jump() and emoticon() illustrate some of the benefits of functions: code reuse, encapsulation, and modularity. We explain each in more detail.

Code Reuse

A fragment of code that is used multiple times in a program—or by multiple programs—can be packaged in a function. That way, the programmer types the code fragment only once, inside a function definition, and then calls the function wherever the code fragment is needed. The program ends up being shorter, with a single function call replacing a code fragment, and clearer, because the name of the function can be more descriptive of the action being performed by the code fragment. Debugging also becomes easier because a bug in the code fragment will need to be fixed only once.

In function emoticon(), we use function jump() four times, making the emoticon() function shorter and more readable. We also make it easier to modify: Any change to how the jump should be done will need to be implemented only once, inside the jump() function. In fact, the function emoticon() would not even need to be modified.

We saw another example of code reuse in the case study at the end of Chapter 6, where we developed a blackjack application. Because shuffling a standard deck of 52 cards and dealing a card to a game participant is common to most card games, we implemented each action in a separate, reusable function.

Modularity (or Procedural Decomposition)

The complexity of developing a large program can be dealt with by breaking down the program into smaller, simpler, self-contained pieces. Each smaller piece (e.g., function) can be designed, implemented, tested, and debugged independently.

We broke the problem of drawing a smiley face into two functions. The function jump() is independent of the function emoticon() and can be tested and debugged independently. Once function jump() has been developed, the function emoticon() is easier to implement. We also used the modular approach to develop the blackjack application using five functions in Chapter 6.

Encapsulation (or Information Hiding)

When using a function in a program, typically the developer does not need to know its implementation details, but only what it does. In fact, removing the implementation details from the developer's radar makes her job easier.

The developer of the function emoticon() does not need to know how function jump() works, just that it lifts turtle t and drops it at coordinates (x, y). This simplifies the process of developing function emoticon(). Another benefit of encapsulation is that if the implementation of function jump() changes (and is made more efficient, for example), the function emoticon() would not have to change.

In the blackjack application, the functions that shuffle the deck and compute the value of a hand encapsulate the code doing the actual work. The benefit here is that the main blackjack program contains meaningful function calls, such as

   deck = shuffledDeck()    # get shuffled deck
and
   dealCard(deck, player)   # deal to player first

rather than code that is harder to read.

Local Variables

There is a potential danger when the developer using a function does not know its implementation details. What if, somehow, the execution of the function inadvertently affects the calling program (i.e., the program that made the function call)? For example, the developer could accidentally use a variable name in the calling program that happens to be defined and used in the executing function. In order to achieve encapsulation, those two variables should be separate. Variable names defined (i.e., assigned) inside a function should be “in-visible” to the calling program: They should be variables that exist only locally, in the context of the execution of the function, and they should not affect variables of the same name in the calling program. This invisibility is achieved thanks to the fact that variables defined inside functions are local variables.

We illustrate this with the next function:

Module: ch7.py
1  def double(y):
2      x = 2
3      print(‘x = {}, y = {}’.format(x,y))
4      return x*y

After running the module ch7, we check that names x and y have not been defined in the interpreter shell:

>>> x
Traceback (most recent call last):
  File “<pyshell#37>”, line 1, in <module>
    x
NameError: name ‘x’ is not defined
>>> y
Traceback (most recent call last):
  File “<pyshell#38>”, line 1, in <module>
    y
NameError: name ‘y’ is not defined

Now let's execute double():

>>> res = double(3)
x = 2, y = 3

During the execution of the function, variables x and y exist: y is assigned 3, and then x is assigned 2. However, after the execution of the function, the names x and y do not exist in the interpreter shell:

>>> x
Traceback (most recent call last):
  File “<pyshell#40>”, line 1, in <module>
    x
NameError: name ‘x’ is not defined
>>> y
Traceback (most recent call last):
  File “<pyshell#41>”, line 1, in <module>
    y
NameError: name ‘y’ is not defined

Clearly x and y exist only during the execution of the function.

Namespaces Associated with Function Calls

Actually, something even stronger is true: The names x and y that are defined during the execution of double() are invisible to the calling program (the interpreter shell in our example) even during the execution of the function. To convince ourselves of this, let's define values x and y in the shell and then execute function double() again:

>>> x,y = 20,30
>>> res = double(4)
x = 2, y = 4

Let's check whether the variables x and y (defined in the interpreter shell) have changed:

>>> x,y
(20, 30)

No, they did not. This example shows that there are two separate pairs of variable names x and y: the pair defined in the interpreter shell and the pair defined during the execution of the function. Figure 7.1 illustrates that the interpreter shell and the executing function double() each has its own, separate space for names. Each space is called a namespace. The interpreter shell has its namespace. Each function call creates a new namespace. Different function calls will have different corresponding namespaces. The net effect is that each function call has its own “execution area” so it does not interfere with the execution of the calling program or other functions.

image

Figure 7.1 Namespaces. Variable names x and y are defined in the interpreter shell. During the execution of double(4) separate, local variables y and x get defined in the namespace of the function call.

Names that are assigned during the execution of a function call are said to be local names, and they are local with respect to a function call. Names that are local to a function exist only in the namespace associated with the function call. They:

  • Are only visible to the code inside the function.
  • Do not interfere with names defined outside of the function, even if they are the same.
  • Exist only during the execution of the function; they do not exist before the function starts execution and they no longer exist after the function completes execution.

Practice Problem 7.1

Define functions f() and g() in this way:

>>> def f(y):
        x = 2
        print(‘In f(): x = {}, y = {}’.format(x,y))
        g(3)
        print(‘In f(): x = {}, y = {}’.format(x,y))

>>> def g(y):
        x = 4
        print(‘In g(): x = {}, y = {}’.format(x,y))

Using Figure 7.1 as your model, show graphically the variables names, their values, and the namespaces of functions f() and g() during the execution of function g() when this call is made:

>>> f(1)

Namespaces and the Program Stack

We know that a new namespace is created for every function call. If we call a function that in turn calls a second function that in turn calls a third function, there would be three names-paces, one for each function call. We now discuss how these namespaces are managed by the operating system (OS). This is important because without OS support for managing namespaces, function calls could not be made.

We use this module as our running example:

Module: stack.py
1  def h(n):
2      print(‘Start h’)
3      print(1/n)
4      print(n)
5
6  def g(n):
7      print(‘Start g’)
8      h(n-1)
9      print(n)
10
11 def f(n):
12     print(‘Start f’)
13     g(n-1)
14     print(n)

After we run the module, we make the function call f(4) from the shell:

>>> f(4)
Start f
Start g
Start h
0.5
2
3
4

Figure 7.2 illustrates the execution of f(4).

image

Figure 7.2 Execution of f(4). The execution starts in the namespace of function call f(4), where n is 4. Function call g(3) creates a new namespace in which n is 3; function g() executes using that value of n. Function call h(2) creates another namespace in which n is 2; function h() uses that value of n. When the execution of h(2) terminates, the execution of g(3) and its corresponding namespace, in which n is 3, is restored. When g(3) terminates, the execution of f(4) is restored.

Figure 7.2 shows the three different namespaces and the different value that n has in each. To understand how these namespaces are managed, we go through the execution of f(4) carefully.

When we start executing f(4), the value of n is 4. When the function call g(3) is made, the value of n in the namespace of the function call g(3) is 3. However, the old value of n, 4, is still needed because the execution of f(4) is not complete; line 14 will need to be executed after g(3) is done.

Before the execution of g(3) gets started, the underlying OS stores all the information necessary to complete the execution of f(4):

  • The value of variable n (in this case, the value n = 4)
  • The line of code where the execution of f(4) should resume at (in this case, line 14)

This information is stored by the OS in an area of main memory called the program stack. It is referred to as a stack because the OS will push the information on top of the program stack before executing g(3), as shown in Figure 7.3.

image

Figure 7.3 Stack frame. A function call stores its local variables in its stack frame; if another function is called, then the line to be executed next is stored too.

The program stack area storing the information related to a specific unfinished function call is called the stack frame.

When function call g(3) starts executing, the value of n is 3. During the execution of g(3), function h() is called on input n-1 = 2. Before the call is made, the stack frame corresponding to g(3) is pushed onto the program stack, as shown in Figure 7.4.

image

Figure 7.4 Program stack. If a function is called inside another function, the stack frame for the called function is stored on top of the stack frame of the calling function.

In Figure 7.5, we again illustrate the execution of function call f(4), but this time we also show how the OS uses the program stack to store the namespace of an unfinished function call so it can restore the namespace when the function call resumes. In the top half of Figure 7.5, the sequence of function calls is illustrated with black arrows. Each call has a corresponding “push” of a frame to the program stack, shown with blue arrows.

Now let's resume our careful analysis of the execution of f(4). When h(2) executes, n is 2 and values 1/n = 0.5 and n = 2 are printed. Then h(2) terminates. At this point, the execution should return to function call g(3). So the namespace associated with g(3) needs to get restored and the execution of g(3) should continue from where it left off. The OS will do this by popping a frame from the top of the program stack and using the values in the frame to:

image

Figure 7.5 Execution of f(4), part 2. The function call f(4) executes in its own namespace. When function call g(3) is made, the namespace of f(4) is pushed onto the program stack. The call g(3) runs in its own namespace. When the call h(2) is made, the namespace of g(3) is also pushed onto the program stack. When function call h(2) terminates, the namespace of g(3) is restored by popping the top stack frame of the program stack; its execution continues from the line stored in the stack frame (i.e., line 9). When g(3) terminates, the namespace of f(4) and its execution are restored by popping the program stack again.

  • Restore the value of n to 3 (i.e., restore the namespace).
  • Continue the execution of g(3) starting with the line 9.

Executing line 9 will result in the printing of n = 3 and the termination of g(3). As shown in Figure 7.5, the program stack is then popped again to restore the namespace of function call f(4) and to continue the execution of f(4) starting at line 14. This results in the printing of n = 4 and the termination of f(4).

image Program Stacks and Buffer Overflow Attacks

The program stack is an essential component of an OS's main memory. The program stack contains a stack frame for every function call. A stack frame is used to store variables (like n) that are local with respect to the function call. Also, when a call to another function is made, the stack frame is used to store the line number (i.e., memory address) of the instruction where execution should be resumed, once that other function terminates.

The program stack also presents a vulnerability in a computer system, one that is often exploited in a type of computer system attack known as the buffer overflow attack. The vulnerability is that the input argument of a function call, say the 4 in f(4), may written into the program stack, as illustrated in Figure 7.5. In other words, the OS allocates a small space in the program stack to store the expected input argument (in our case, an integer value).

A malicious user could call the function with an argument that is much larger than the allocated space. This argument could contain nefarious code and would also overwrite one of the existing line numbers in the program stack with a new line number. This new line number would, of course, point to the nefarious code.

Eventually, the executing program will pop the stack frame containing the overwritten line number and start executing instructions starting from that line.

7.2 Global versus Local Namespaces

We have seen that every function call has a namespace associated with it. This namespace is where names defined during the execution of the function live. We say that the scope of these names (i.e., the space where they live) is the namespace of the function call.

Every name (whether a variable name, function name, type name—and not just a local name) in a Python program has a scope, that is, a namespace where it lives. Outside of its scope, the name does not exist, and any reference to it will result in an error. Names assigned inside (the body of) a function are said to have local scope (local with respect to a function call), which means that their namespace is the one associated with the function call.

Names assigned in the interpreter shell or in a module outside of any function are said to have global scope. Their scope is the namespace associated with the shell or the whole module. Variables with global scope are referred to as global variables.

Global Variables

When you execute a Python statement in the interpreter shell, you are doing so in a name-space associated with the shell. In this context, this namespace is the global namespace, and the variables defined in it, such as a in

>>> a = 0
>>> a
0

are global variables whose scope is global.

When you execute a module, whether from within or from outside your integrated development environment, there is a namespace associated with the executing module. This namespace is the global namespace during the execution of the module. Any variable that is defined in the module outside of any function, such as a in the one-line module scope.py

Module: scope.py
1 # a really small module
2 a = 0

is a global variable.

Variables with Local Scope

We use a sequence of examples to illustrate the difference between global and local scopes. Our first example is this strange module:

Module: scope1.py
1  def f(b):      # f has global scope, b has local scope
2      a = 6      # this a has scope local to function call f()
3      return a*b # this a is the local a
4
5  a = 0          # this a has global scope
6  print(‘f(3) = {}’.format(f(3)))
7  print(‘a is {}’.format(a))       # global a is still 0

When we run this module, the function definition is executed first, and then the last three lines of the module are executed in succession. Names f and a have global scope. When function f(3) is called in line 6, local variables b and then a get defined in the namespace of the function call f(3). The local variable a is unrelated to the global name a, as shown in Figure 7.6.

image

Figure 7.6 Local variables. In line 5, integer 0 is assigned to global variable name a. During execution of function call f(3) in line 6, a separate variable a, local with respect to the function call, gets defined and is assigned integer 3.

This is printed when the module executes:

>>>
f(3) = 18
a is 0

Note that when evaluating the product a*b while executing f(3), the local name a is used.

Variables with Global Scope

To get our next example, we remove line 2 from module scope1:

Module: scope2.py
1  def f(b):
2      return a*b             # this a is the global a
3
4  a = 0                      # this a has global scope
5  print(‘f(3) = {}’.format(f(3)))
6  print(‘a is {}’.format(a)) # global a is still 0

When we run the module scope2, function call f(3) will be made. Figure 7.7 shows the variable names, and the namespaces they are defined in, when function call f(3) executes.

When the product a*b is evaluated during the execution of f(3), no local variable a exists in the namespace associated with function call f(3). The variable a that is used is now the global variable a, whose value is 0. When you run this example, you get:

image

Figure 7.7 Global variables. During the execution of function call f(3) in line 5, variable a is evaluated when computing the product a*b. Because no name a exists in the function call namespace, the name a defined in the global namespace is used.

>>>
f(3) = 0
a is 0

How does the Python interpreter decide whether to evaluate a name as a local or as a global name?

Whenever the Python interpreter needs to evaluate a name (of a variable, function, etc.), it searches for the name definition in this order:

  1. First the enclosing function call namespace
  2. Then the global (module) namespace
  3. Finally the namespace of module builtins

In our first example, module scope1, name a in product a*b evaluated to a local name; in the second example, module scope2, because no name a was defined in the local name-space of the function call, a evaluates to the global name a.

Built-in names (such as sum(), len(), print(), etc.) are names that are predefined in the module builtins that Python automatically imports upon start-up. (We discuss this built-in module in more detail in Section 7.4.) Figure 7.8 shows the different namespaces that exist when the function call f(3) gets executed in module scope2. function f()

image

Figure 7.8 Searching for a name definition. Three namespaces exist during the execution of f(3) when running module scope2. Whenever the Python interpreter needs to evaluate a name, it starts the search for the name in the local namespace. If the name is not found there, it continues the search in the global namespace. If the name is not found there either, the name search then moves to the builtins namespace.

Figure 7.8 illustrates how names are evaluated during the execution of statement print(a*b) in line 2 of function f() while executing f(3). The execution of print(a*b) involves three name searches, all starting with the local namespace of function call f(3):

  1. The Python interpreter first searches for name a. First it looks in the local namespace of function f(3). Since it is not there, it looks next in the global namespace, where it finds name a.
  2. The search for name b starts and ends in the local namespace.
  3. The search for (function) name print starts in the local namespace, continues through the global namespace, and ends, successfully, in the namespace of module builtins.

Changing Global Variables Inside a Function

In our last example, we consider this situation: Suppose that in function f() of module scope1, the intention of statement a = 0 was to modify the global variable a. As we saw in module scope1, the statement a = 0 inside function f() will instead create a new local variable of the same name. If our intention was to have the function change the value of a global variable, then we must use the global reserved keyword to indicate that a name is global. We use this module to explain the keyword global:

Module: scope3.py
1  def f(b):
2      global a        # all references to a in f() are to the global a
3      a = 6           # global a is changed
4      return a*b      # this a is the global a
5
6  a = 0           # this a has global scope
7  print(‘f(3) = {}’.format(f(3)))
8  print(‘a is {}’.format(a))       # global a has been changed to 6

In line 3, the assignment a = 6 changes the value of the global variable a because the statement global a specifies that the name a is global rather than local. This concept is illustrated in Figure 7.9.

image

Figure 7.9 Keyword global. During execution of f(3), the assignment a = 6 is executed. Because name a is defined to refer to the global name a, it is the global a that gets assigned. No name a is created in the local namespace of the function call.

When you run the module, the modified value of global variable a is used to compute f(3):

>>>
f(3) = 18
a is 6

Practice Problem 7.2

For each name in the next module, indicate whether it is a global name or whether it is local in f(x) or local in g(x).

1  def f(y):
2      x = 2
3      return g(x)
4
5  def g(y):
6      global x
7      x = 4
8      return x*y
9
10 x = 0
11 res = f(x)
12 print(‘x = {}, f(0) = {}’.format(x, res))

7.3 Exceptional Control Flow

While the focus of the discussion in this chapter has been on namespaces, we have also touched on another fundamental topic: how the operating system and namespaces support the “normal” execution control flow of a program, and especially function calls. We consider, in this section, what happens when the “normal” execution control flow gets interrupted by an exception and ways to control this exceptional control flow. This section also continues the discussion of exceptions we started in Section 4.4.

Exceptions and Exceptional Control Flow

The reason why error objects are called exceptions is because when they get created, the normal execution flow of the program (as described by, say, the program's flowchart) is interrupted, and the execution switches to the so called exceptional control flow (which the flowchart typically does not show as it is not part of the normal program execution). The default exceptional control flow is to stop the program and print the error message contained in the exception object.

We illustrate this using the functions f(), g(), and h() we defined in Section 7.1. In Figure 7.2, we illustrated the normal flow of execution of function call f(4). In Figure 7.10, we illustrate what happens when we make the function call f(2) from the shell.

The execution runs normally all the way to function call h(0). During the execution of h(0), the value of n is 0. Therefore, an error state occurs when the expression 1/n is evaluated. The interpreter raises a ZeroDivisionError exception and creates a ZeroDivisionError exception object that contains information about the error.

The default behavior when an exception is raised is to interrupt the function call in which the error occurred. Because the error occurred while executing h(0), the execution of h(0) is interrupted. However, the error also occurred during the execution of function calls g(1) and f(2), and the execution of both is interrupted as well. Thus, statements shown in gray in Figure 7.10 are never executed.

image

Figure 7.10 Execution of f(2). The normal execution control flow of function call f(2) from the shell is shown with black arrows: f(2) calls g(1), which, in turn, calla h(0). When the evaluation of expression 1/n = 1/0 is attempted, a ZeroDivisionError exception is raised. The normal execution control flow is interrupted: Function call h(0) does not run to completion, and neither do g(1) or f(2). The exceptional control flow is shown with a dashed arrow. Statements that are not executed are shown in gray. Since call f(2) is interrupted, the error information is output in the shell.

When execution returns to the shell, the information contained in the exception object is printed in the shell:

Traceback (most recent call last):
  File “<pyshell#116>”, line 1, in <module>
    f(2)
  File “/Users/me/ch7.py”, line 13, in f
    g(n-1)
  File “/Users/me/ch7.py”, line 8, in g
    h(n-1)
  File “/Users/me/ch7.py”, line 3, in h
    print(1/n)
ZeroDivisionError: division by zero

In addition to the type of error and a friendly error message, the output also includes a traceback, which consists of all the function calls that got interrupted by the error.

Catching and Handling Exceptions

Some programs should not terminate when an exception is raised: server programs, shell programs, and pretty much any program that handles requests. Since these programs receive requests from outside the program (interactively from the user or from a file), it is difficult to ensure that the program will not enter an erroneous state because of malformed input. These programs need to continue providing their service even if internal errors occur. What this means is that the default behavior of stopping the program when an error occurs and printing an error message must be changed.

We can change the default exceptional control flow by specifying an alternate behavior when an exception is raised. We do this using the try/except pair of statements. The next small application illustrates how to use them:

Module: age1.py
1 strAge = input(‘Enter your age: ’)
2 intAge = int(strAge)
3 print(‘You are {} years old.’.format(intAge))

The application asks the user to interactively enter her age. The value entered by the user is a string. This value is converted to an integer before being printed. Try it!

This program works fine as long as the user enters her age in a way that makes the conversion to an integer possible. But what if the user types “fifteen” instead?

>>>
Enter your age: fifteen
Traceback (most recent call last):
  File “/Users/me/age1.py”, line 2, in <module>
    intAge = int(strAge)
ValueError: invalid literal for int() with base 10: ‘fifteen’

A ValueError exception is raised because string ‘fifteen’ cannot be converted to an integer.

Instead of “crashing” while executing the statement age = int(strAge), wouldn't it be nicer if we could tell the user that they were supposed to enter their age using decimal digits. We can achieve this using the next try and except pair of statements:

Module: age2.py
1  try:
2      # try block --- executed first; if an exception is
3      # raised, the execution of the try block is interrupted
4      strAge = input(‘Enter your age: ’)
5      intAge = int(strAge)
6      print(‘You are {} years old.’.format(intAge))
7  except:
8      # except block --- executed only if an exception
9      # is raised while executing the try block
10     print(‘Enter your age using digits 0-9!’)

The try and except statements work in tandem. Each has an indented code block below it. The code block below the try statement, from line to 5, is executed first. If no errors occur, then the code block below except is ignored:

>>>
Enter your age: 22
You are 22 years old.

If, however, an exception is raised during the execution of a try code block (say, strAge cannot be converted to an integer), the Python interpreter will skip the execution of the remaining statements in the try code block and execute the code block of the except statement (i.e., line 9) instead:

>>>
Enter your age: fifteen
Enter your age using digits 0-9!

Note that the first line of the try block got executed, but not the last.

The format of a try/except pair of statements is:

try:
    <indented code block 1>
except:
    <indented code block 2>
<non-indented statement>

The execution of <indented code block 1> is attempted first. If it goes through without any raised exceptions, then <indented code block 2> is ignored and execution continues with <non-indented statement>. If, however, an exception is raised during the execution of <indented code block 1>, then the remaining statements in <indented code block 1> are not executed; instead <indented code block 2> is executed. If <indented code block 2> runs to completion without a new exception being raised, then the execution continues with <non-indented statement>.

The code block <indented code block 2> is referred to as the exception handler, because it handles a raised exception. We will also say that an except statement catches an exception.

The Default Exception Handler

If a raised exception is not caught by an except statement (and thus not handled by a user-defined exception handler), the executing program will be interrupted and the traceback and information about the error are output. We saw this behavior when we ran module age1.py and entered the age as a string:

>>>
Enter your age: fifteen
Traceback (most recent call last):
  File “/Users/me/age1.py”, line 2, in <module>
    intAge = int(strAge)
ValueError: invalid literal for int() with base 10: ‘fifteen’

This default behavior is actually the work of Python's default exception handler. In other words, every raised exception will be caught and handled, if not by a user-defined handler then by the default exception handler.

Catching Exceptions of a Given Type

In the module age2.py, the except statement can catch an exception of any type. The except statement could also be written to catch only a certain type of exception, say ValueError exceptions:

Module: age3.py
1  try:
2      # try block
3      strAge = input(‘Enter your age: ’)
4      intAge = int(strAge)
5      print(‘You are {} years old.’.format(intAge))
6 except ValueError:
7      # except block --- executed only if a ValueError
8      # exception is raised in the try block
9      print(‘Enter your age using digits 0-9!’)

If an exception is raised while executing the try code block, then the exception handler is executed only if the type of the exception object matches the exception type specified in the corresponding except statement (ValueError in this case). If an exception is raised that does match the type specified in the except statement, then the except statement will not catch it. Instead, the default exception handler will handle it.

Multiple Exception Handlers

There could be not just one but several except statements following one try statement, each with its own exception handler. We illustrate this with the next function readAge(), which attempts to open a file, read the first line, and convert it to an integer in a single try code block.

Module: ch7.py
1  def readAge(filename):
2      ‘‘‘converts first line of file filename to
3         an integer and prints it’’’
4      try:
5          infile = open(filename)
6          strAge = infile.readline()
7          age = int(strAge)
8          print(‘age is’,age)
9      except IOError:
10         # executed only if an IOError exception is raised
11         print(‘Input/Output error.’)
12     except ValueError:
13         # executed only if a ValueError exception is raised
14         print(‘Value cannot be converted to integer.’)
15     except:
16         # executed if an exception other than IOError
17         # or ValueError is raised
18         print(‘Other error.’)

Several types of exceptions could be raised while executing the try code block in function readAge. The file might not exist:

>>> readAge(‘agg.txt’)
Input/Output error.

In this case, what happened was that an IOError exception got raised while executing the first statement of the try code block; the remaining statements in the code section were skipped and the IOError exception handler got executed.

Another error could be that the first line of the file age.txt does not contain something that can be converted to an integer value:

File: age.txt
>>> readAge(‘age.txt’)
Value cannot be converted to integer

The first line of file age.txt is ‘fifteen ’, soa ValueError exception is raised when attempting to convert it to an integer. The associated exception handler prints the friendly message without interrupting the program.

The last except statement will catch any exception that the first two except statements did not catch.

image Maiden Flight of Ariane 5

On June 4, 1996, the Ariane 5 rocket developed over many years by the European Space Agency flew its first test flight. Seconds after the launch, the rocket exploded.

The crash happened when an overflow exception got raised during a conversion from floating-point to integer. The cause of the crash was not the unsuccessful conversion (it turns out that it was of no consequence); the real cause was that the exception was not handled. Because of this, the rocket control software crashed and shut the rocket computer down. Without its navigation system, the rocket started turning uncontrollably, and the onboard monitors made the rocket self-destruct.

This was probably one of the most expensive computer bugs in history.

Practice Problem 7.3

Create a “wrapper” function safe-open() for the open() function. Recall that when open() is called to open a file that doesn't exist in the current working directory, an exception is raised:

>>> open(‘ch7.px’, ‘r’)

Traceback (most recent call last):
  File “<pyshell#19>”, line 1, in <module>
    open(‘ch7.px’, ‘r’)
IOError: [Errno 2] No such file or directory: ‘ch7.px’

If the file exist, a reference to the opened file object is returned:

>>> open(‘ch7.py’, ‘r’)
<_io.TextIOWrapper name=‘ch7.py’ encoding=‘US-ASCII’>

When safe-open() is used to open a file, a reference to the opened file object should be returned if no exception is raised, just like for the open() function. If an exception is raised while trying to open the file, safe-open() should return None.

>>> safe-open(‘ch7.py’, ‘r’)
<_io.TextIOWrapper name=‘ch7.py’ encoding=‘US-ASCII’>
>>> safe-open(‘ch7.px’, ‘r’)
>>>

Controlling the Exceptional Control Flow

We started this section with an example illustrating how a raised exception interrupts the normal flow of a program. We now look at ways to manage the exceptional flow using appropriately placed exception handlers. We again use the functions f(), g(), and h() defined in module stack.py, shown next, as our running example.

Module: stack.py
 1  def h(n):
 2      print(‘Start h’)
 3      print(1/n)
 4      print(n)
 5
 6  def g(n):
 7      print(‘Start g’)
 8      h(n-1)
 9      print(n)
10
11  def f(n):
12      print(‘Start f’)
13      g(n-1)
14      print(n)

In Figure 7.10, we showed how the evaluation of f(2) causes an exception to be raised. The ZeroDivisionError exception is raised when an attempt is made to evaluate 1/0 while executing h(0). Since the exception object is not caught in function calls h(0), g(1), and f(2), these function calls are interrupted, and the default exception handler handles the exception, as shown in Figure 7.10.

Suppose we would like to catch the raised exception and handle it by printing ‘Caught!’ and then continuing with the normal flow of the program. We have several choices where to write a try code block and catch the exception. One approach is to to put the outermost function call f(2) in a try block (see also Figure 7.11):

>>> try:
        f(2)
except:
        print(‘Caught!’)

image

Figure 7.11 Execution of f(2) with an exception handler. We run f(2) in a try code block. The execution runs normally until an exception is raised while executing h(0). The normal flow of execution is interrupted: Function call h(0) does not run to completion, and neither do g(1) or f(2). The dashed arrow shows the exceptional execution flow. Statements that are not executed are shown in gray. The except statement corresponding to the try block catches the exception and the matching handler handles it.

The execution in Figure 7.11 parallels the one illustrated in Figure 7.10 until the point when function call f(2), made from the shell, is interrupted because of a raised exception. Because the function call was made in a try block, the exception is caught by the corresponding except statement and handled by its exception handler. The resulting output includes the string ‘Caught!’ printed by the handler:

Start f
Start g
Start h
Caught!

Compare this to the execution shown in Figure 7.10, when the default exception handler handled the exception.

In the previous example, we chose to implement an exception handler at the point where function f(2) is called. This represents a design decision by the developer of function f() that it is up to the function user to worry about handling exceptions.

In the next example, the developer of function h makes the design decision that function h() should handle any exception that occur during its execution. In this example, the function h() is modified so that its code is inside a try block:

Module: stack2.py
1  def h(n):
2      try:
3           print(‘Start h’)
4           print(1/n)
5           print(n)
6      except:
7           print(‘Caught!’)

(Functions f() and g() remain the same as in stack.py.) When we run f(2), we get:

>>> f(2)
Start f
Start g
Start h
Caught!
1
2

Figure 7.12 illustrates this execution. The execution parallels the one in Figure 7.11 until the exception is raised when evaluating 1/0. Since the evaluation is now inside a try block, the corresponding except statement catches the exception. The associated handler prints ‘Caught!’. When the handler is done, the normal execution control flow resumes, and function call h(0) runs to completion as do g(1) and f(2).

Practice Problem 7.4

What statements in module stack.py are not executed when running f(2), assuming these modifications are made in stack.py:

  1. Add a try statement that wraps the line print(1/n) in h() only.
  2. Add a try statement that wraps the three lines of code in g().
  3. Add a try statement that wraps the line h(n-1) in g() only.

In each case, the exception handler associated with the try block just prints ‘Caught!’.

image

Figure 7.12 Execution of f(2) with an exception handler inside h(). The normal execution flow is shown with black arrows. When an attempt is made to evaluate 1/n = 1/0, a ZeroDivisionError exception is raised and the normal flow of execution is interrupted. The dashed arrow shows the exceptional flow of execution, and statements that are not executed are shown in gray. Since the exception occurred in a try block, the corresponding except statement catches the exception, and its associated handler handles it. The normal flow of execution then resumes, with h(0), g(1), and h(2) all running to completion.

7.4 Modules as Namespaces

So far, we have used the term module to describe a file containing Python code. When the module is executed (imported), then the module is (also) a namespace. This namespace has a name, which is the name of the module. In this namespace will live the names that are defined in the global scope of the module: the names of functions, values, and classes defined in the module. These names are all referred to as the module's attributes.

Module Attributes

As we have already seen, to get access to all the functions in the Standard Library module math, we import the module:

>>> import math

Once a module is imported, the Python built-in function dir() can be used to view all the module's attributes:

>>> dir(math)
[‘_ _doc_ _’, ‘_ _file_ _’, ‘_ _name_ _’, ‘_ _package_ _’, ‘acos’,
‘acosh’, ‘asin’, ‘asinh’, ‘atan’, ‘atan2’, ‘atanh’, ‘ceil’,
‘copysign’, ‘cos’, ‘cosh’, ‘degrees’, ‘e’, ‘exp’, ‘fabs’,
‘factorial’, ‘floor’, ‘fmod’, ‘frexp’, ‘fsum’, ‘hypot’, ‘isinf’,
‘isnan’, ‘ldexp’, ‘log’, ‘log10’, ‘log1p’, ‘modf’, ‘pi’, ‘pow’,
‘radians’, ‘sin’, ‘sinh’, ‘sqrt’, ‘tan’, ‘tanh’, ‘trunc’]

(The list may be slightly different depending on the version of Python you are using.) You can recognize many of the math functions and constants we have been using. Using the familiar notation to access the names in the module, you can view the objects these names refer to:

>>> math.sqrt
<built-in function sqrt>
>>> math.pi
3.141592653589793

We can now understand what this notation really means: math is a namespace and the expression math.pi, for example, evaluates the name pi in the namespace math.

image “Other” Imported Attributes

The output of the dir() function shows that there are attributes in the math namespace module that are clearly not math functions or constants: _ _doc_ _, _ _file_ _, _ _name_ _, and _ _package_ _. These names exist for every imported module. These names are defined by the Python interpreter at import time and are kept by the Python interpreter for bookkeeping purposes.

The name of the module, the absolute pathname of the file containing the module, and the module docstring are stored in variables _ _name_ _, _ _file_ _, and _ _doc_ _, respectively.

What Happens When Importing a Module

When the Python interpreter executes an import statement, it:

  1. Looks for the file corresponding to the module.
  2. Runs the module's code to create the objects defined in the module.
  3. Creates a namespace where the names of these objects will live.

We discuss the first step in detail next. The second step consists of executing the code in the module. This means that all Python statements in the imported module are executed from top to bottom. All assignments, function definitions, class definitions, and import statements will create objects (whether integer or string objects, or functions, or modules, or classes) and generate the attributes (i.e., names) of the resulting objects. The names will be stored in a new namespace whose name is typically the name of the module.

Module Search Path

Now we look into how the interpreter finds the file corresponding to the module to be imported. An import statement only lists a name, the name of the module, without any directory information or .py suffix. Python uses a Python search path to locate the module. The search path is simply a list of directories (folders) where Python will look for modules. The variable name path defined in the Standard Library module sys refers to this list. You can thus see what the (current) search path is by executing this in the shell:

>>> import sys
>>> sys.path
[‘/Users/me/Documents’, …]

(We omit the long list of directories containing the Standard Library modules.) The module search path always contains the directory of the top-level module, which we discuss next, and also the directories containing the Standard Library modules. At every import statement, Python will search for the requested module in each directory in this list, from left to right. If Python cannot find the module, then an ImportError exception is raised.

For example, suppose we want to import the module example.py that is stored in home directory /Users/me (or whatever directory you saved the file example.py in):

Module: example.py
1  ‘an example module’
2  def f():
3      ‘function f’
4
       print(‘Executing f()’)
5
6  def g():
7      ‘function g’
8      print(‘Executing g()’)
9
10 x = 0  # global var

Before we import the module, we run function dir() to check what names are defined in the shell namespace:

>>> dir()
[‘_ _builtins_ _’, ‘_ _doc_ _’, ‘_ _name_ _‘, ’_ _package_ _’]

The function dir(), when called without an argument, returns the names in the current namespace, which in this case is the shell namespace. It seems only “bookkeeping” names are defined. (Read the next Detour about the name _ _builtins_ _.)

Now let's try to import the module example.py:

>>> import example
Traceback (most recent call last):
  File “<pyshell#24>”, line 1, in <module>
    import example
  ImportError: No module named example

It did not work because directory /Users/me is not in list sys.path. So let's append it:

>>> import sys
>>> sys.path.append(‘/Users/me’)

and try again:

>>> import example
>>> example.f
<function f at 0x15e7d68>
>>> example.x
0

It worked. Let's run dir() again and check that the module example has been imported:

>>> dir()
[‘_ _builtins_ _’, ‘_ _doc_ _’, ‘_ _name_ _’, ‘__package_ _’, ‘example’,
‘sys’]

image Module builtins

The name __builtins__ refers to the namespace of the builtins module, which we referred to in Figure 7.8.

The builtins module contains all the built-in types and functions and is usually imported automatically upon starting Python. You can check that by listing the attributes of module builtins using the dir() function:

>>> dir(_ _builtins_ _)
[‘ArithmeticError’, ‘AssertionError’, …, ‘vars’, ‘zip’]

Note: Use dir( _ _)builtins_ _), not dir(‘_ _builtins_ _’).

Practice Problem 7.5

Find the random module in one of the directories listed in sys.path, open it, and find the implementations of functions randrange(), random(), and sample(). Then import the module into the interpreter shell and view its attributes using the dir() function.

Top-Level Module

A computer application is a program that is typically split across multiple files (i.e., modules). In every Python program, one of the modules is special: It contains the “main program” by which we mean the code that starts the application. This module is referred to as the top-level module. The remaining modules are essentially “library” modules that are imported by the top-level module and contain functions and classes that are used by the application.

We have seen that when a module is imported, the Python interpreter creates a few “bookkeeping” variables in the module namespace. One of these is variable _ _name_ _. Python will set its value in this way:

  • If the module is being run as a top-level module, attribute _ _name_ _ is set to the string _ _main_ _.
  • If the file is being imported by another module, whether the top-level or other, attribute _ _name_ _ is set to the module's name.

We use the next module to illustrate how _ _name_ _ is assigned:

Module: name.py
1  print(‘My name is {}’.format(_ _name_ _))

When this module is executed by running it from the shell (e.g., by hitting image in the IDLE shell), it is run as the main program (i.e., the top-level module):

>>>
My name is _ _main_ _

So the _ _name_ _ attribute of the imported module is set to _ _main_ _.

Top-Level Module and the Module Search Path image

In the last subsection, we mentioned that the directory containing the top-level module is listed in the search path. Let's check that this is indeed the case. First run the previous module name.py that was saved in, say, directory /Users/me. Then check the value of sys.path:

>>> import sys
>>> sys.path
[‘/Users/me’, ‘/Users/lperkovic/Documents’, …]

Note that directory /Users/me is in the search path.

The module name is also the top-level module when it is run at the command line:

> python name.py
My name is _ _main_ _

If, however, another module imports module name, then module name will not be top level. In the next import statement, the shell is the top-level program that imports the module name.py:

>>> import name
My name is name

Here is another example. The next module has only one statement, a statement that imports module name.py:

Module: import.py
1 import name

When module import.py is run from the shell, it is run as the main program that imports module name.py:

>>>
My name is name

In both cases, the _ _name_ _ attribute of the imported module is set to the name of the module.

The _ _name_ _ attribute of a module is useful for writing code that should be executed only when the module is run as the top-level module. This would be the case, for example, if the module is a “library” module that contains function definitions and the code is used for debugging. All we need to do is make the debugging code a code block of this if statement:

if _ _name_ _ == ‘_ _main_ _’:
    # code block

If the module is run as a top-level module, the code block will be executed; otherwise it will not.

Practice Problem 7.6

Add code to module example.py that calls the functions defined in the module and prints the values of variables defined in the module. The code should execute when the module is run as a top-level module only, such as when it is run from the shell:

>>>
Testing module example:
Executing f()
Executing g()
0

Different Ways to Import Module Attributes

We now describe three different ways to import a module and its attributes, and we discuss the relative benefits of each. We again use the module example as our running example:

Module: example.py
1 ‘an example module’
2 def f():
3     print(‘Executing f()’)
4
5 def g():
6     print(‘Executing g()’)
7
8 x = 0 # global var

One way to get access to functions f() or g(), or global variable x, is to:

>>> import example

This import statement will find the file example.py and run the code in it. This will instantiate two function objects and one integer object and create a namespace, called example, where the names of the created objected will be stored. In order to access and use the module attributes, we need to specify the module namespace:

>>> example.f()
Executing f()

As we have seen, calling f() directly would result in an error. Therefore, the import statement did not bring name f into the namespace of module _ _main_ _ (the module that imported example); it only brings the name of the module example, as illustrated in Figure 7.13.

image

Figure 7.13 Importing a module. The statement import example creates name example in the calling module namespace which will refer to the namespace associated with the imported module example.

image

Figure 7.14 Importing a module attribute. Module attributes can be imported into the calling module namespace. The statement from example import f creates name f in the calling module namespace that refers to the appropriate function object.

Instead of importing the name of the module, it is also possible to import the names of the needed attributes themselves using the from command:

>>> from example import f

As illustrated in Figure 7.14, from copies the name of attribute f to the scope of the main program, the module doing the import, so that f can be referred to directly, without having to specify the module name.

>>> f()
Executing f()

Note that this code copies only the name of attribute f, not of attribute g (see Figure 7.14). Referring to g directly results in an error:

>>> g()
Traceback (most recent call last):
  File “<pyshell#7>”, line 1, in <module>
    g()
NameError: name ‘g’ is not defined

Finally, is is also possible to use from to import all the attributes of a module using the wild card *:

>>> from example import *
>>> f()
Executing f()
>>> x
0

Figure 7.15 shows that all the attributes of example are copied to the namespace _ _main_ _.

image

Figure 7.15 Importing all the module's attributes. The statement from example import * imports all the attributes of example into the calling module namespace.

Which way is best? That might not be the right question. Each of the three approaches has some benefits. Just importing the module name has the benefit of keeping the names in the module in a namespace separate from the main module. This guarantees that there will be no clash between a name in the main module and the same name in the imported module.

The benefit of importing individual attributes from the module is that we do not have to use the namespace as a prefix when we refer to the attribute. This helps make the code less verbose and thus more readable. The same is true when all module attributes are imported using import *, with the additional benefit of doing it succinctly. However, it is usually not a good idea to use import * because we may inadvertently import a name that clashes with a global name in the main program.

7.5 Classes as Namespaces

In Python, a namespace is associated with every class. In this section we explain what that means. We discuss, in particular, how Python uses namespaces in a clever way to implement classes and class methods.

But first, why should we care how Python implements classes? We have been using Python's built-in classes without ever needing to look below the hood. There will be times, however, when we will want to have a class that does not exist in Python. Chapter 8 explains how to develop new classes. There it will be very useful to know how Python uses namespaces to implement classes.

A Class Is a Namespace

Underneath the hood, a Python class is essentially a plain old namespace. The name of the namespace is the name of the class, and the names stored in the namespace are the class attributes (e.g., the class methods). For example, the class list is a namespace called list that contains the names of the methods and operators of the list class, as shown in Figure 7.16.

image

Figure 7.16 The namespace list and its attributes. The class list defines a namespace that contains the names of all list operators and methods. Each name refers to the appropriate function object.

Recall that to access an attribute of an imported module, we need to specify the name-space (i.e., the module name) in which the attribute is defined:

>>> import math
>>> math.pi
3.141592653589793

Similarly, the attributes of the class list can be accessed by using list as the namespace:

>>> list.pop
<method ‘pop’ of ‘list’ objects>
>>> list.sort
<method ‘sort’ of ‘list’ objects>

Just as for any other namespace, you can use the built-in function dir() to find out all the names defined in the list namespace:

>>> dir(list)
[‘_ _add_ _’, ‘_ _class_ _’, ‘_ _contains_ _’, ‘_ _delattr_ _’,
…,
‘index’, ‘insert’, ‘pop’, ‘remove’, ‘reverse’, ‘sort’]

These are names of the operators and methods of the list class.

Class Methods Are Functions Defined in the Class Namespace

We now look at how class methods are implemented in Python. We continue to use the class list as our running example. Suppose, for example, that you would like to sort this list:

>>> lst = [5,2,8,1,3,6,4,7]

In Chapter 2, we learned how to do this:

>>> lst.sort()

We know now that function sort() is really a function defined in the namespace list. In fact, when the Python interpreter executes the statement

>>> lst.sort()

the first thing it will do is translate the statement to

>>> list.sort(lst)

Try executing both statements and you will see that the result is the same!

When method sort() is invoked on the list object lst, what really happens is that the function sort(), defined in namespace list, is called on list object lst. More generally, Python automatically maps the invocation of a method by an instance of a class, such as

instance.method(arg1, arg2, …)

to a call to a function defined in the class namespace and using the instance as the first argument:

class.method(instance, arg1, arg2, …)

where class is the type of instance. This last statement is the statement that is actually executed.

Let's illustrate this with a few more examples. The method invocation by list lst

>>> lst.append(9)

gets translated by the Python interpreter to

>>> list.append(lst, 9)

The method invocation by dictionary d

>>> d.keys()

gets translated to

>>> dict.keys(d)

From these examples, you can see that the implementation of every class method must include an additional input argument, corresponding to the instance calling the method.

Chapter Summary

This chapter covers programming language concepts and constructs that are key to managing program complexity. The chapter builds on the introductory material on functions and parameter passing from Sections 3.3 and 3.5 and sets up a framework that will be useful when learning how to develop new Python classes in Chapter 8 and when learning how recursive functions execute in Chapter 10.

One of the main benefits of functions—encapsulation—follows from the black box property of functions: Functions do not interfere with the calling program other than through the input arguments (if any) and returned values (if any). This property of functions holds because a separate namespace is associated with each function call, and thus a variable name defined during the execution of the function call is not visible outside of that function call.

The normal execution control flow of a program, in which functions call other functions, requires the management of function call namespaces by the OS through a program stack. The program stack is used to keep track of the namespaces of active function calls. When an exception is raised, the normal control flow of the program is interrupted and replaced by the exceptional control flow. The default exceptional control flow is to interrupt every active function call and output an error message. In this chapter, we introduce exception handling, using the try/except pair of statements, as a way to manage the exceptional control flow and, when it makes sense, use it as part of the program.

Namespaces are associated with imported modules as well as classes and, as shown in Chapter 8, objects as well. The reason for this is the same as for functions: Components of a program are easier to manage if they behave like black boxes that do not interfere with each other in unintended ways. Understanding Python classes as namespaces is particularly useful in the next chapter, where we learn how to develop new classes.

Solutions to Practice Problems

7.1 During the execution of g(3), function call f(1) has not terminated yet and has a namespace associated with it; in this namespace, local variable names y and x are defined, with values 1 and 2, respectively. Function call g(3) also has a namespace associated with it, containing different variable names y and x, referring to values 3 and 4, respectively. function f()

image

7.2 The answers are shown as inline comments:

def f(y):       # f is global, y is local to f()
    x = 2       # x is local to f()
    return g(x) # g is global, x is local to f()

def g(y):       # g is global, y is local to g()
    global x    # x is global
    x = 4       # x is global
    return x*y  # x is global, y is local to g()

x = 0           # x is global
res = f(x)      # res, f and x are global
print(‘x = {}, f(0) = {}’.format(x, res)) # same here

7.3 The function should take the same arguments as the open() function. The statements that open the file and return the reference to the opened file should be in the try code section. The exception handler should just return None.

def safe-open(filename, mode):
    ‘returns handle to opened file filename, or None if error occurred’
    try:
        # try block
        infile = open(filename, mode)
        return infile
    except:
        # exept block
        return None

7.4 These statements are not executed:

  1. Every statement is executed.
  2. The last statements in h() and g().
  3. The last statements in h().

7.5 On Windows, the folder containing the module random is C:\Python3xlib, where x can be 1, 2, or other digit, depending on the version of Python 3 you are using; on a Mac, it is /Library/Frameworks/Python.Framework/Versions/3.x/lib/python31.

7.6 This code is added at the end of file example.py:

if __name__ == ‘_ _main_ _’:
     print(‘Testing module example:’)
     f()
     g()
     print(x)

Exercises

7.7 Using Figure 7.5 as your model, illustrate the execution of function call f(1) as well as the state of the program stack. Function f() is defined in module stack.py.

7.8 What is the problem with the next program?

Module: probA.py
1 print(f(3))
2 def f(x):
3     return 2*x+1

Does the next program exhibit the same problem?

Module: probB.py
1 def g(x):
2     print(f(x))
3
4 def f(x):
5     return 2*x+1
6
7 g(3)

7.9 The blackjack application developed in Section 6.5 consists of five functions. Therefore, all variables defined in the program are local. However, some of the local variables are passed as arguments to other functions, and the objects they refer to are therefore (intentionally) shared. For each such object, indicate in which function the object was created and which functions have access to it.

7.10 This exercise relates to modules one, two, and three:

Module: one.py
1  import two
2
3  def f1():
4      two.f2()
5
6  def f4():
7      print(‘Hello!’)
Module: two.py
1  import three
2
3  def f2():
4      three.f3()
Module: three.py
1  import one
2
3  def f3():
4      one.f4()

When module one is imported into the interpreter shell, we can execute f1():

>>> import one
>>> one.f1()
Hello!

(For this to work, list sys.path should include the folder containing the three modules.) Using Figures 7.13 as your model, draw the namespaces corresponding to the three imported modules and also the shell namespace. Show all the names defined in the three imported namespaces as well as the objects they refer to.

7.11 After importing one in the previous problem, we can view the attributes of one:

>>> dir(one)
[‘_ _builtins_ _’, ‘_ _doc_ _’, ‘_ _file_ _’, ‘_ _name_ _’, ‘_ _package_ _’,
‘f1’, ‘f4’, ‘two’]

However, we cannot view the attributes of two in the same way:

>>> dir(two)
Traceback (most recent call last):
  File “<pyshell#202>”, line 1, in <module>
    dir(two)
NameError: name ‘two’ is not defined

Why is that? Note that importing module one forces the importing of modules two and three. How can we view their attributes using function dir()?

7.12 Using Figure 7.2 as your model, illustrate the execution of function call one.f1(). Function f1() is defined in module one.py.

7.13 Modify the module blackjack.py from Section 6.5 so that when the module is run as the top module, the function blackjack() is called (in other words, a blackjack game starts). Test your solution by running the program from your system's command-line shell:

image

7.14 Let list lst be:

>>> lst = [2,3,4,5]

Translate the next list method invocations to appropriate calls to functions in namespace list:

  1. lst.sort()
  2. lst.append(3)
  3. lst.count(3)
  4. lst.insert(2, 1)

7.15 Translate the following string method invocations to functions calls in namespace str:

  1. ‘error'.upper()
  2. ‘2,3,4,5’.split(‘,’)
  3. ‘mississippi’.count(‘i’)
  4. ‘bell’.replace(‘e’, ‘a’)
  5. ‘ ’.format(1, 2, 3)

Problems

7.16 The first input argument of function index() in Problem 6.27 is supposed to be the name of a text file. If the file cannot be found by the interpreter or if it cannot be read as a text file, an exception will be raised. Reimplement function index() so that the message shown here is printed instead:

>>> index(‘rven.txt’, [‘raven’, ‘mortal’, ‘dying’, ‘ghost’])
File ‘rven.txt’ not found.

7.17 In Problem 6.34, you were asked to develop an application that asks users to solve addition problems. Users were required to enter their answers using digits 0 through 9.

Reimplement the function game() so it handles nondigit user input by printing a friendly message like “Please write your answer using digits 0 though 9. Try again!” and then giving the user another opportunity to enter an answer.

>>> game(3)
8 + 2 =
Enter answer: ten
Please write your answer using digits 0 though 9. Try again!
Enter answer: 10
Correct.

7.18 The blackjack application developed in Section 6.5 includes the function dealCard() that pops the top card from the deck and passes it to a game participant. The deck is implemented as a list of cards, and popping the top card from the deck corresponds to popping the list. If the function is called on an empty deck, an attempt to pop an empty list is made, and an IndexError exception is raised.

Modify the blackjack application by handling the exception raised when trying to deal a card from an empty deck. Your handler should create a new shuffled deck and deal a card from the top of this new deck.

7.19 Implement function inValues() that asks the user to input a set of nonzero floating-point values. When the user enters a value that is not a number, give the user a second chance to enter the value. After two mistakes in a row, quit the program. Add all correctly specified values when the user enters 0. Use exception handling to detect improper inputs.

>>> inValues()
Please enter a number: 4.75
Please enter a number: 2,25
Error. Please re-enter the value.
Please enter a number: 2.25
Please enter a number: 0
7.0
>>> inValues()
Please enter a number: 3.4
Please enter a number: 3,4
Error. Please re-enter the value.
Please enter a number: 3,4
Two errors in a row. Quitting …

7.20 In Problem 7.19, the program quits only when the user makes two mistakes in a row. Implement the alternative version of the program that quits when the user makes the second mistake, even if it follows a correct entry by the user.

7.21 If you type image while the shell is executing the input() function, a KeyboardInterrupt exception will be raised. For example:

>>> x = input()        # Typing Ctrl-C
Traceback (most recent call last):
  File “<stdin >”, line 1, in <module>
KeyboardInterrupt

Create a wrapper function safe_input() which works just like function input() except that it returns None when an exception is raised.

>>> x = safe_input() # Typing Ctrl-C
>>> x                # x is None
>>> x = safe_input() # Typing 34
34
>>> x                # x is 34
‘34’
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset