So far, we have introduced numbers, assignments, input/output, comparisons, and looping constructs. How powerful is this subset of Python? In a theoretical sense, it is as powerful as you will ever need, i.e., it is Turing complete. This means that if a problem can be solved via computation, it can be solved using only those statements you have already seen.
This is a reasonable piece of code, but it lacks general utility. It works only for values denoted by the variables x and epsilon. This means that if we want to reuse it, we need to copy the code, possibly edit the variable names, and paste it where we want it. Because of this we cannot easily use this computation inside of some other, more complex, computation.
Furthermore, if we want to compute cube roots rather than square roots, we have to edit the code. If we want a program that computes both square and cube roots (or for that matter square roots in two different places), the program would contain multiple chunks of almost identical code. This is a very bad thing. The more code a program contains, the more chance there is for something to go wrong, and the harder the code is to maintain. Imagine, for example, that there was an error in the initial implementation of square root, and that the error came to light when testing the program. It would be all too easy to fix the implementation of square root in one place and forget that there was similar code elsewhere that was also in need of repair.
Python provides several linguistic features that make it relatively easy to generalize and reuse code. The most important is the function.
4.1 Functions and Scoping
We’ve already used a number of built-in functions, e.g., max and abs in Figure 4.1. The ability for programmers to define and then use their own functions, as if they were built-in, is a qualitative leap forward in convenience.
4.1.1 Function Definitions
In Python each function definition is of the form
def name of function (list of formal parameters): body of function
For example, we could define the function maxVal by the code
def maxVal(x, y):
if x > y:
return x
else:
return y
def
is a reserved word that tells Python that a function is about to be defined. The function name (maxVal in this example) is simply a name that is used to refer to the function.
The sequence of names within the parentheses following the function name (x, y
in this example) are the formal parameters of the function. When the function is used, the formal parameters are bound (as in an assignment statement) to the actual parameters (often referred to as arguments) of the function invocation (also referred to as a function call). For example, the invocation:
maxVal(3, 4)
binds x to 3 and y to 4.
The function body is any piece of Python code. There is, however,a special statement, return, that can be used only within the body of a function.
A function call is an expression, and like all expressions it has a value. That value is the value returned by the invoked function. For example, the value of the expression maxVal(3,4)*maxVal(3,2) is 12
, because the first invocation of maxVal returns theint 4
and the second returns theint 3
. Note that execution of a return statement terminates an invocation of the function.
To recapitulate, when a function is called:
The expressions that make up the actual parameters are evaluated, and the formal parameters of the function are bound to the resulting values. For example, the invocation maxVal(3+4, z) will bind the formal parameter x to 7 and the formal parameter y to whatever value the variable z has when the invocation is evaluated.
The point of execution (the next instruction to be executed) moves from the point of invocation to the first statement in the body of the function.
The code in the body of the function is executed until either a return statement is encountered, in which case the value of the expression following the return becomes the value of the function invocation, or there are no more statements to execute, in which case the function returns the value None. (If no expression follows the return, the value of the invocation is None.)
The value of the invocation is the returned value.
The point of execution is transferred back to the code immediately following the invocation.
Parameters provide something called lambda abstraction, allowing programmers to write code that manipulates not specific objects, but instead whatever objects the caller of the function chooses to use as actual parameters.
4.1.2 Keyword Arguments and Default Values
In Python, there are two ways that formal parameters get bound to actual parameters. The most common method, which is the only one we have used thus far, is called positional—the first formal parameter is bound to the first actual parameter, the second formal to the second actual, etc. Python also supports keyword arguments, in which formals are bound to actuals using the name of the formal parameter. Consider the function definition:
def printName(firstName, lastName, reverse):
if reverse:
print(lastName + ', ' + firstName)
else:
print(firstName, lastName)
The function printName
assumes that firstName and lastName are strings and that reverse
is a Boolean. If reverse == True
, it prints lastName, firstName, otherwise it prints firstName lastName
.
Each of the following is an equivalent invocation of printName
:
printName('Olga', 'Puchmajerova', False)
printName('Olga', 'Puchmajerova', reverse = False)
printName('Olga', lastName = 'Puchmajerova', reverse = False) printName(lastName = 'Puchmajerova', firstName = ' Olga', reverse = False)
Though the keyword arguments can appear in any order in the list of actual parameters, it is not legal to follow a keyword argument with a non-keyword argument. Therefore, an error message would be produced by:
printName('Olga', lastName = 'Puchmajerova', False)
Keyword arguments are commonly used in conjunction with default parameter values. We can, for example, write:
def printName(firstName, lastName, reverse = False):
if reverse:
print(lastName + ', ' + firstName)
else:
print(firstName, lastName)
Default values allow programmers to call a function with fewer than the specified number of arguments. For example,
printName('Olga', 'Puchmajerova')
printName('Olga', 'Puchmajerova', True)
printName('Olga', 'Puchmajerova', reverse = True)
will print
Olga Puchmajerova
Puchmajerova, Olga
Puchmajerova, Olga
The last two invocations of printName
are semantically equivalent. The last one has the advantage of providing some documentation for the perhaps mysterious argument True
.
4.1.3 Scoping
Let’s look at another small example,
def f(x): #name x used as formal parameter
y = 1
x = x + y
print("x = %s" %x)
return x
x = 3
y =2
z = f(x) #value of x used as actual parameter
print('z =', z)
print('x =', x)
print('y =', y)
# When run, this code prints,
x = 4
z = 4
x = 3
y = 2
What is going on here? At the call of f
, the formal parameterx
is locally bound to the value of the actual parameterx
. It is important to note that though the actual and formal parameters have the same name, they are not the same variable. Each function defines a new name space, also called a scope
. The formal parameter x and the local variable y that are used in f exist only within the scope of the definition of f
. The assignment statement x = x + y
within the function body binds the local name x
to the object 4
. The assignments inf
have no effect at all on the bindings of the namesx
and y
that exist outside the scope of f
.
Here’s one way to think about this:
- At top level, i.e., the level of the shell, a symbol table keeps track of all names defined at that level and their current bindings.
- When a function is called, a new symbol table (often called a stack frame 栈帧) is created. This table keeps track of all names defined within the function (including the formal parameters) and their current bindings. If a function is called from within the function body, yet another stack frame is created.
- When the function completes, its stack frame goes away.
In Python, one can always determine the scope of a name by looking at the program text. This is called static or lexical scoping. Figure 4.2 contains an example illustrating Python’s scope rules.
Figure 4.2 Nested scopes
def f(x):
def g():
x = 'abc'
print('x1 =', x)
def h():
z = x
print('z1 =', z)
x = x + 1
print('x2 =', x)
h()
g()
print('x3 =', x)
return g
x = 3
z = f(x)
print('x4 =', x)
print('z2 =', z)
z()
So, what does the code in Figure 4.2 print? It prints
x2 = 4
z1 = 4
x1 = abc
x3 = 4
x4 = 3
z2 = <function f.<locals>.g at 0x10188d0d0>
The history of the stack frames associated with the code is depicted in Figure 4.3. The first column contains the set of names known outside the body of the function f, i.e., the variables x and z, and the function name f. The first assignment statement binds x to 3.
def f():
print('x1 =', x)
def g():
print('x2 =', x)
x = 1
x = 3
f()
x = 3
g()
# When run, this code prints,
x1 = 3
Traceback (most recent call last):
File "4.FunctionScoping.py", line 43, in <module>
g()
File "4.FunctionScoping.py", line 37, in g
print('x2 =', x)
UnboundLocalError: local variable 'x' referenced before assignment
It prints 3 when f is invoked, but the error message UnboundLocalError: local variable 'x' referenced before assignment
is printed when the print statement in g
is encountered. This happens because the assignment statement following the print
statement causes x to be local to g. And because x
is local tog
, it has no value when the print
statement is executed.
Confused yet? It takes most people a bit of time to get their head around scope rules. Don’t let this bother you. For now, charge ahead and start using functions. Most of the time you will find that you only want to use variables that are local to a function, and the subtleties of scoping will be irrelevant.
4.2 Specifications
Figure 4.4 defines a function, findRoot, that generalizes the bisection search we used to find square roots in Figure 4.1. It also contains a function, testFindRoot, that can be used to test whether or not findRoot works as intended.
Figure 4.4 Finding an approximation to a root
print('Figure 4.4 Finding an approximation to a root')
def findRoot(x, power, epsilon):
"""Assume x and epsilon int or float, power an int,
epsilon > 0 and power >= 1
Return float y such that y**power is with epsilon of x.
If such a float does not exists, it returns None"""
if x < 0 and power%2 == 0: #Negative number has no even-powered roots.
return None
low = min(-1.0, x)
high = max(1.0, x)
ans = (high + low)/2
while abs(ans**power -x) >= epsilon:
if ans**power < x:
low = ans
else:
high = ans
ans = (high + low)/2.0
return ans
def testFindRoot():
epsilon = 0.01**2
for x in [0.25, -0.25, 2, -2, 8, -8]:
for power in range(1, 4):
print('Testing x=%s, and power=%s' %(x, power))
result = findRoot(x, power, epsilon)
if result == None:
print(' No root')
else:
print(' %s ** %s ~= %s' %(result, power, x))
testFindRoot()
# When runs, the code print:
The function testFindRoot
is almost as long as findRoot
itself. To inexperienced programmers, writing test functions such as this often seems to be a waste of effort. Experienced programmers know, however, that an investment in writing testing code often pays big dividends. It certainly beats typing test cases into the shell over and over again during debugging (the process of finding out why a program does not work, and then fixing it). It also forces us to think about which tests are likely to be most illuminating.
The text between the triple quotation marks is called a docstring in Python. By convention, Python programmers use docstrings to provide specifications of functions. These docstrings can be accessed using the built-in function help. For example, if we enter the shell and type help(abs), the system will display:
Help on built-in function abs in module built-ins:
abs(x) Return the absolute value of the argument.
If the code in Figure 4.4 has been loaded into an IDE, typing help(findRoot)
in the shell will display:
findRoot(x, power, epsilon)
Assumes x and epsilon int or float, power an int,
epsilon > 0 & power >= 1 Returns float y such that y**power is within epsilon of x. If such a float does not exist, it returns None
If we type findRoot()
in the editor, the list of formal parameters will be displayed.
A specification of a function defines a contract between the implementer of a function and those who will be writing programs that use the function. We will refer to the users of a function as its clients. This contract can be thought of as containing two parts:
-
Assumptions条件:** These describe conditions that must be met by clients of the function. Typically, they describe constraints on the actual parameters. Almost always, they specify the acceptable set of types for each parameter, and not infrequently some constraints on the value of one or more of the parameters. For example, the first two lines of the docstring of
findRoot
describe the assumptions that must be satisfied by clients offindRoot
. -
Guarantees功能:** These describe conditions that must be met by the function, provided that it has been called in a way that satisfies the assumptions. The last two lines of the
docstring
of findRoot describe the guarantees that the implementation of the function must meet.
Functions are a way of creating computational elements that we can think of as primitives. Just as we have the built-in functions max
and abs
, we would like to have the equivalent of a built-in function for finding roots and for many other complex operations. Functions facilitate this by providing decomposition and abstraction.
- Decomposition creates structure. It allows us to break a program into parts that are reasonably self-contained, and that may be reused in different settings.
- Abstraction hides detail. It allows us to use a piece of code as if it were a black box—that is, something whose interior details we cannot see, don’t need to see, and shouldn’t even want to see. The essence of abstraction is preserving information that is relevant in a given context, and forgetting information that is irrelevant in that context. ==The key to using abstraction effectively in programming is finding a notion of relevance that is appropriate for both the builder of an abstraction and the potential clients of the abstraction==. That is the true art of programming.
==Abstraction is all about forgetting.== There are lots of ways to model this, for example, the auditory apparatus of most teenagers.
Teenager says: May I borrow the car tonight? Parent says: Yes, but be back before midnight, and make sure that the gas tank is full.
Teenager hears: Yes.
The teenager has ignored all of those pesky details that he or she considers irrelevant. Abstraction is a many-to-one process. Had the parent said Yes, but be back before 2:00 a.m., and make sure that the car is clean, it would also have been abstracted to Yes.
By way of analogy, imagine that you were asked to produce an introductory computer science course containing twenty-five lectures. One way to do this would be to recruit twenty-five professors and ask each of them to prepare a one-hour lecture on their favorite topic. Though you might get twenty-five wonderful hours, the whole thing is likely to feel like a dramatization of Pirandello’s “Six Characters in Search of an Author” (or that political science course you took with fifteen guest lecturers). If each professor worked in isolation, they would have no idea ==how to relate the material in their lecture to the material covered in other lectures==.
Somehow, one needs to let everyone know what everyone else is doing, without generating so much work that nobody is willing to participate. ==This is where abstraction comes in==. You could write twenty- five specifications, each saying what material the students should learn in each lecture, but not giving any detail about how that material should be taught. What you got might not be pedagogically wonderful, but at least it might make sense.
This is the way organizations go about using teams of programmers to get things done. Given a specification of a module, a programmer can work on implementing that module without worrying unduly about what the other programmers on the team are doing. Moreover, the other programmers can use the specification to start writing code that uses that module without worrying unduly about how that module is to be implemented.
The specification of findRoot
is an abstraction of all the possible implementations that meet the specification. Clients of findRoot
can assume that the implementation meets the specification, but they should assume nothing more. For example, clients can assume that the call findRoot(4.0, 2, 0.01)
returns some value whose square is between 3.99 and 4.01. The value returned could be positive or negative, and even though 4.0 is a perfect square, the value returned might not be 2.0 or -2.0.
4.3 Recursion
You may have heard of recursion, and in all likelihood think of it as a rather subtle programming technique. That’s a charming urban legend spread by computer scientists to make people think that we are smarter than we really are. Recursion is a very important idea, but it’s not so subtle, and it is more than a programming technique.
As a descriptive method recursion is widely used, even by people who would never dream of writing a program. Consider part of the legal code of the United States defining the notion of a “natural-born” citizen. Roughly speaking, the definition is as follows:
- Any child born inside the United States,
- Any child born in wedlock outside the United States both of whose parents are citizens of the U.S., as long as one parent has lived in the U.S. prior to the birth of the child, and
- Any child born in wedlock outside the United States one of whose parents is a U.S. citizen who has lived at least five years in the U.S. prior to the birth of the child, provided that at least two of those years were after the citizen’s fourteenth birthday.
The first part is simple; if you are born in the United States, you are a natural-born citizen (such as Barack Obama). If you are not born in the U.S., then one has to decide if your parents are U.S. citizens (either natural born or naturalized). To determine if your parents are U.S. citizens, you might have to look at your grandparents, and so on.
In general, a recursive definition is made up of two parts. There is at least one base case that directly specifies the result for a special case (case 1 in the example above), and there is at least one recursive (inductive) case (cases 2 and 3 in the example above) that defines the answer in terms of the answer to the question on some other input, typically a simpler version of the same problem.
The world’s simplest recursive definition is probably the factorial function (typically written in mathematics using !) on natural numbers. The classic inductive definition is 1! = 1
(n + 1)! = (n + 1) ∗ n!
The first equation defines the base case. The second equation defines factorial for all natural numbers, except the base case, in terms of the factorial of the previous number.
Figure 4.5 contains both an iterative (factI)
and a recursive(factR)
implementation of factorial.
Figure 4.5 Iterative and recursive implementations of factorial
print('4.Recursive')
def factI(n):
"""Assumen an int > 0
Return n!"""
result = 1
while n > 1:
result = result * n
n -= 1
return result
def factR(n):
if n == 1:
return n
else:
return n*factR(n - 1)
This function is sufficiently simple that neither implementation is hard to follow. Still, the second is a more obvious translation of the original recursive definition.
It almost seems like cheating to implementfactR
by calling factR
from within the body of factR
. It works for the same reason that the iterative implementation works. We know that the iteration in factI will terminate because n starts out positive and each time around the loop it is reduced by 1. This means that it cannot be greater than 1 forever. Similarly, if factR is called with 1, it returns a value without making a recursive call. When it does make a recursive call, it always does so with a value one less than the value with which it was called. Eventually, the recursion terminates with the call factR(1).
4.3.1 Fibonacci Numbers
This definition is a little different from the recursive definition of factorial:
- It has two base cases, not just one. In general, you can have as many base cases as you need.
- In the recursive case, there are two recursive calls, not just one. Again, there can be as many as you need.
Figure 4.7 contains a straightforward implementation of the Fibonacci recurrence, along with a function that can be used to test it.
Figure 4.7 Recursive implementation of Fibonacci sequence
print('Fibonacci Number')
def fib(n):
"""Assume n int >= 0
Return Fibonacci of n """
if n == 0 or n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
def testFib(n):
for i in range(n+1):
print('fib of %s = %s' %(i, fib(i)))
testFib(10)
testFib(2)
testFib(3)
Writing the code is the easy part of solving this problem. Once we went from the vague statement of a problem about bunnies to a set of recursive equations, the code almost wrote itself. ==Finding some kind of abstract way to express a solution== to the problem at hand is very often the hardest step in building a useful program. We will talk much more about this later in the book.
4.3.2 Palindromes
Recursion is also useful for many problems that do not involve numbers. Figure 4.8 contains a function, isPalindrome
, that checks whether a string reads the same way backwards and forwards.
The function isPalindrome
contains two internalhelper functions
. This should be of no interest to clients of the function, who should care only that isPalindrome
meets its specification. But you should care, because there are things to learn by examining the implementation.
The helper function toChars
converts all letters to lowercase and removes all non-letters. It starts by using a built-in method on strings to generate a string that is identical to s, except that all uppercase letters have been converted to lowercase. We will talk a lot more about method invocation
when we get to classes. For now, think of it as a peculiar syntax for a function call. Instead of putting the first (and in this case only) argument inside parentheses following the function name, we use dot notation
to place that argument before the function name.
Figure 4.8 Palindrome testing
print('isPalindrome')
def isPalindrome(s):
""""Assume s is a str
Return True if letters i s form a palindrome; False
otherwise. Non_letters and capitalization are ignored."""
def toChars(s):
s = s.lower():
letters = ''
for c in s:
if c in 'abcdefghigklmnopqrstuvwxyz':
letters = letters + c
return letters
def isPal(s):
if len(s) <= 1:
return True
else:
return s[0] == s[-1] and isPal(s[1:-1]) #Note slice停止的位置。
return isPal(toChars(s))
The helper function isPal
uses recursion to do the real work. The two base cases are strings of length zero or one. This means that the recursive part of the implementation is reached only on strings of length two or more. The conjunction in the else clause is evaluated from left to right. The code first checks whether the first and last characters are the same, and if they are goes on to check whether the string minus those two characters is a palindrome. That the second conjunct is not evaluated unless the first conjunct evaluates to True is semantically irrelevant in this example. However, later in the book we will see examples where this kind of short-circuit evaluation of Boolean expressions is semantically relevant.
This implementation of isPalindrome
is an example of an important problem-solving principle known as divide-and-conquer. (This principle is related to but slightly different from divide-and- conquer algorithms, which are discussed in Chapter 10.) The problem- solving principle is to conquer a hard problem by breaking it into a set of subproblems with the properties that:
- the subproblems are easier to solve than the original problem,
- and solutions of the subproblems can be combined to solve the original problem.
Divide-and-conquer is a very old idea. Julius Caesar practiced what the Romans referred to as divide et impera (divide and rule). The British practiced it brilliantly to control the Indian subcontinent. Benjamin Franklin was well aware of the British expertise in using this technique, prompting him to say at the signing of the U.S. Declaration of Independence, “We must all hang together, or assuredly we shall all hang separately.”
In this case, we solve the problem by breaking the original problem into a simpler version of the same problem (checking whether a shorter string is a palindrome) and a simple thing we know how to do (comparing single characters), and then combine the solutions with and. Figure 4.9 contains some code that can be used to visualize how this works.
Return的问题稍后研究,跟直觉不同,颇有意思。
关注与直觉不相符之处。
def toChars(s):
s = s.lower()
letters = ''
for c in s:
if c in 'abcdefghijklmnopqrstuvwxyz':
letters = letters + c
return letters
print('letters3 = %s' %letters)
Figure 4.9 Code to visualize palindrome testing
def isPalindrome(s):
""""Assume s is a str
Return True if letters i s form a palindrome; False otherwise.
Punctuation marks, blanks,and capitalization are ignored."""
def toChars(s):
s = s.lower()
letters = ''
for c in s:
if c in 'abcdefghijklmnopqrstuvwxyz':
letters = letters + c
return letters #最有意思的竟然是这里,与直觉不相符的地方。
def isPal(s):
# print(' isPal called with %r' %s)
print(' isPal called with', s)
if len(s) <= 1:
print(' About to return True from base case.')
return True
else:
answer = s[0] == s[-1] and isPal(s[1:-1]) #Note slice停止的位置。
print(' About to return', answer, 'for', s)
return answer
return isPal(toChars(s))
def testIsPalindrome():
print('Try dogGod')
print(isPalindrome('dogGod'))
print('Try doGood')
print(isPalindrome('doGood'))
testIsPalindrome() #调用函数
#When run, this code prints:
Try dogGod
isPal called with doggod
isPal called with oggo
isPal called with gg
isPal called with
About to return True from base case.
About to return True for gg
About to return True for oggo
About to return True for doggod
True
Try doGood
isPal called with dogood
isPal called with ogoo
isPal called with go
About to return False for go
About to return False for ogoo
About to return False for dogood
False
4.4 Global Variables
If you tried calling fib with a large number, you probably noticed that it took a very long time to run. Suppose we want to know how many recursive calls are made? We could do a careful analysis of the code and figure it out, and in Chapter 9 we will talk about how to do that. Another approach is to add some code that counts the number of calls. One way to do that uses global variables.
Until now, all of the functions we have written communicate with their environment solely through their parameters and return values. For the most part, this is exactly as it should be. It typically leads to programs that are relatively easy to read, test, and debug. Every once in a while, however, global variables come in handy. Consider the code in Figure 4.10.
In each function, the line of code global numFibCalls
tells Python that the name numCalls
should be defined at the outermost scope of the module (see Section 4.5) in which the line of code appears rather than within the scope of the function in which the line of code appears. Had we not included the code globalnumFibCalls
, the name numFibCalls
would have been local to each of the functions fib and testFib
, because numFibCalls
occurs on the left-hand side of an assignment statement in both fib and testFib
. The functions fib and testFib both have unfettered access to the object referenced by the variable numFibCalls
. The functiontestFib
binds numFibCalls
to 0 each time it calls fib
, and fib increments the value ofnumFibCalls
each time fib is entered.
Figure 4.10 Using a global variable
def fib(x):
"""Assume x an int >= 0
Return Fibonacci of x"""
global numFibcalls
numFibcalls += 1
if x == 0 or x == 1:
return 1 #the base case.
else:
return fib(x-1) + fib(x-2)
def testFib(n):
for i in range(n+1):
global numFibcalls
numFibcalls = 0
print('fib of %s = %s' %(i, fib(i)))
print('fib called %s times' %numFibcalls)
testFib(5)
#When run,it prints:
fib of 0 = 1
fib called 1 times
fib of 1 = 1
fib called 1 times
fib of 2 = 2
fib called 3 times
fib of 3 = 3
fib called 5 times
fib of 4 = 5
fib called 9 times
fib of 5 = 8
fib called 15 times
It is with some trepidation that we introduce the topic of global variables. Since the 1970s card-carrying computer scientists have inveighed against them. The indiscriminate use of global variables can lead to lots of problems. The key to making programs readable is locality. One reads a program a piece at a time, and the less context needed to understand each piece, the better. Since global variables can be modified or read in a wide variety of places, the sloppy use of them can destroy locality. Nevertheless, there are times when they are just what is needed.
4.5 Modules
So far, we have operated under the assumption that our entire program is stored in one file. This is perfectly reasonable as long as programs are small. As programs get larger, however, it is typically more convenient to store different parts of them in different files. Imagine, for example, that multiple people are working on the same program. It would be a nightmare if they were all trying to update the same file. Python modules allow us to easily construct a program from code in multiple files.
A module is a .py file containing Python definitions and statements. We could create, for example, a file circle.py
containing the code in Figure 4.11.
Figure 4.11 Some code related to circles and spheres
pi = 3.13159
def area(radius):
return pi * (radius**2)
def circumference(radius):
return 2 * pi * radius
def sphereSurface(radius):
return 4.0 * area(radius)
def sphereVolume(radius):
return(4/3) * pi * (radius**3)
A program gets access to a module through an import statement. So, for example, the code:
import circle
pi = 3
print(pi)
print(circle.pi)
print(circle.area(3))
print(circle.circumference(3))
print(circle.sphereVolume(3))
#will print:
3
3.13159
28.18431
18.789540000000002
112.73724
Modules are typically stored in individual files. Each module has its own private symbol table. Consequently, within circle.py
we access objects (e.g., pi and area) in the usual way. Executing import
M creates a binding for module M in the scope in which the import appears. Therefore, in the importing context we use dot notation to indicate that we are referring to a name defined in the imported module. For example, outside of circle.py
, the references pi
and
circle.pi
can (and in this case do) refer to different objects.
At first glance, the use of dot notation may seem cumbersome. On the other hand, when one imports a module one often has no idea what local names might have been used in the implementation of that module. The use of dot notation to fully qualify names avoids the possibility of getting burned by an accidental name clash. For example, executing the assignment pi = 3 outside of the circle
module does not change the value of pi used within the circle
module.
There is a variant of the import
statement that allows the importing program to omit the module name when accessing names defined inside the imported module. Executing the statement from M import *
creates bindings in the current scope to all objects defined within M, but not to M itself. For example, the code:
from circle import *
print(pi)
print(circle.pi)
will first print 3.14159, and then produce the error message
NameError: name 'circle' is not defined
Some Python programmers frown upon using this form of import because they believe that it makes code more difficult to read.
As we have seen, a module can contain executable statements as well as function definitions. Typically, these statements are used to initialize the module. For this reason, the statements in a module are executed only the first time a module is imported into a program. Moreover, a module is imported only once per interpreter session. If you start up a shell, import a module, and then change the contents of that module, the interpreter will still be using the original version of the module. This can lead to puzzling behavior when debugging. When in doubt, start a new shell. There are lots of useful modules that come as part of the standard Python library. For example, it is rarely necessary to write your own implementations of common mathematical or string functions. A description of this library can be found at http://docs.python.org/2/library/.
4.6 Files
Every computer system uses files to save things from one computation to the next. Python provides many facilities for creating and accessing files. Here we illustrate some of the basic ones.
Each operating system (e.g., Windows and MAC OS) comes with its own file system for creating and accessing files. Python achieves operating-system independence by accessing files through something called a file handle. The code
nameHandle = open('kids', 'w')
instructs the operating system to create a file with the name kids
, and return a file handle for that file. The argument'w'
to open indicates that the file is to be opened for writing. The following code opens a file, uses the write method to write two lines, and then closes the file. It is important to remember to close the file when the program is finished using it. Otherwise there is a risk that some or all of the writes may not be saved.
nameHandleW = open('kids', 'w')
for i in range(2):
name = input('Enter name: ')
nameHandle.write(name + '\n')
nameHandle.close()
nameHandleR = open('kids', 'r')
In a Python string, the escape character “\” is used to indicate that the next character should be treated in a special way. In this example, the string '\n' indicates a newline character. We can now open the file for reading (using the argument 'r'), and print its contents. Since Python treats a file as a sequence of lines, we can use a for statement to iterate over the file’s contents.
nameHandleR = open('kids', 'r')
for line in nameHandleR:
print(line)
nameHandleR.close()
#If we had typed in the names David and Andrea, this will print
David
Andrea
The extra line between David and Andrea is there because print starts a new line each time it encounters the '\n' at the end of each line in the file. We could have avoided printing that by writing print line[:-1]
. The code:
nameHandle = open('kids', 'w')
nameHandle.write('Michael\n')
nameHandle.write('Mark\n')
nameHandle.close()
nameHandle = open('kids','r')
for line in nameHandle:
print(line[:-1])
nameHandle.close()
Notice that we have overwritten the previous contents of the file kids. If we don’t want to do that we can open the file for appending (instead of writing) by using the argument 'a'
. For example, if we now run the code:
nameHandle = open('kids', 'a')
nameHandle.write('David\n')
nameHandle.write('Andrea\n')
nameHandle.close()
nameHandle = open('kids','r')
for line in nameHandle:
print(line[:-1])
nameHandle.close()
#will print
Michael
Mark
David
Andrea
Some of the common operations on files are summarized in Figure 4.12.