See Section 3.13 for the exercises.
Interaction. Assuming Python is configured properly, interaction should look something like the following. You can run this any way you like: in IDLE, from a shell prompt, and so on:
%python
...copyright information lines... >>>"Hello World!"
'Hello World!' >>> # Ctrl-D or Ctrl-Z to exit, or window close
Programs. Your code (i.e., module) file module1.py and shell interactions should look like:
print 'Hello module world!'
% python module1.py
Hello module world!
Again, feel free to run this other ways—by clicking its icon, by IDLE’s Edit/RunScript menu option, and so on.
Modules. The following interaction listing illustrates running a module file by importing it.
%python
>>>import module1
Hello module world! >>>
Remember that you need to reload the module to run again without stopping and restarting the interpreter. The questions about moving the file to a different directory and importing it again is a trick question: if Python generates a module1.pyc file in the original directory, it uses that when you import the module, even if the source code file (.py) has been moved to a directory not on Python’s search path. The .pyc file is written automatically if Python has access to the source file’s directory and contains the compiled byte-code version of a module. See Part V for more on modules.
Scripts. Assuming your platform supports the
#!
trick, your solution will look like the
following (although your #!
line may need to list
another path on your machine):
#!/usr/local/bin/python (or #!/usr/bin/env python) print 'Hello module world!' %chmod +x module1.py
%module1.py
Hello module world!
Errors. The interaction below demonstrates the
sort of error messages you get when you complete this exercise.
Really, you’re triggering Python exceptions; the
default exception handling behavior terminates the running Python
program and prints an error message and stack trace on the screen.
The stack trace shows where you were in a program when the exception
occurred. In Part VII, you will learn that you
can catch exceptions using try
statements and
process them arbitrarily; you’ll also see that
Python includes a full-blown source code debugger for special error
detection requirements. For now, notice that Python gives meaningful
messages when programming errors occur (instead of crashing
silently):
%python
>>>1 / 0
Traceback (innermost last): File "<stdin>", line 1, in ? ZeroDivisionError: integer division or modulo >>> >>>x
Traceback (innermost last): File "<stdin>", line 1, in ? NameError: x
Breaks. When you type this code:
L = [1, 2] L.append(L)
you create a cyclic data structure in Python. In Python releases
before Version 1.5.1, the Python printer wasn’t
smart enough to detect cycles in objects, and it would print an
unending stream of [1, 2, [1, 2, [1, 2, [1,
2
,—and so on, until you hit the break key combination
on your machine (which, technically, raises a keyboard-interrupt
exception that prints a default message). Beginning with Python
Version 1.5.1, the printer is clever enough to detect cycles and
prints [[...]]
instead.
The reason for the cycle is subtle and requires information you will
gain in Part II. But in short, assignment in
Python always generates references to objects (which you can think of
as implicitly followed pointers). When you run the first assignment
above, the name L
becomes a named reference to a
two-item list object. Python lists are really arrays of object
references, with an append
method that changes the
array in place by tacking on another object reference. Here, the
append
call adds a reference to the front of
L
at the end of L
, which leads
to the cycle illustrated in Figure B-1. Believe it
or not, cyclic data structures can sometimes be useful (but not when
printed!).
See Section 7.10 for the exercises.
The basics. Here are the sort of results you
should get, along with a few comments about their meaning. Note that
;
is used in a few of these to squeeze more than
one statement on a single line; the ;
is a
statement separator.
Numbers >>>2 ** 16 # 2 raised to the power 16
65536 >>>2 / 5, 2 / 5.0 # Integer / truncates, float / doesn't
(0, 0.40000000000000002) Strings >>>"spam" + "eggs" # Concatenation
'spameggs' >>>S = "ham"
>>>"eggs " + S
'eggs ham' >>>S * 5 # Repetition
'hamhamhamhamham' >>>S[:0] # An empty slice at the front--[0:0]
'' >>>"green %s and %s" % ("eggs", S) # Formatting
'green eggs and ham' Tuples >>>('x',)[0] # Indexing a single-item tuple
'x' >>>('x', 'y')[1] # Indexing a 2-item tuple
'y' Lists >>>L = [1,2,3] + [4,5,6] # List operations
>>>L, L[:], L[:0], L[-2], L[-2:]
([1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6], [ ], 5, [5, 6]) >>>([1,2,3]+[4,5,6])[2:4]
[3, 4] >>>[L[2], L[3]] # Fetch from offsets; store in a list
[3, 4] >>>L.reverse( ); L # Method: reverse list in-place
[6, 5, 4, 3, 2, 1] >>>L.sort( ); L # Method: sort list in-place
[1, 2, 3, 4, 5, 6] >>>L.index(4) # Method: offset of first 4 (search)
3 Dictionaries >>>{'a':1, 'b':2}['b'] # Index a dictionary by key.
2 >>>D = {'x':1, 'y':2, 'z':3}
>>>D['w'] = 0 # Create a new entry.
>>>D['x'] + D['w']
1 >>>D[(1,2,3)] = 4 # A tuple used as a key
(immutable)
>>>D
{'w': 0, 'z': 3, 'y': 2, (1, 2, 3): 4, 'x': 1} >>>D.keys( ), D.values( ), D.has_key((1,2,3)) # Methods
(['w', 'z', 'y', (1, 2, 3), 'x'], [0, 3, 2, 4, 1], 1) Empties >>>[[ ]], ["",[ ],( ),{ },None] # Lots of nothings: empty objects
([[ ]], ['', [ ], ( ), { }, None])
Indexing and slicing. Indexing out-of-bounds
(e.g., L[4]
) raises an error; Python always checks
to make sure that all offsets are within the bounds of a sequence.
On the other hand, slicing out of bounds (e.g.,
L[-1000:100]
) works, because Python scales
out-of-bounds slices so that they always fit
(they’re set to zero and the sequence length, if
required).
Extracting a sequence in reverse—with the lower bound greater
than the higher bound (e.g.,
L[3:1]
)—doesn’t really
work. You get back an empty slice ([ ]
), because
Python scales the slice limits to make sure that the lower bound is
always less than or equal to the upper bound (e.g.,
L[3:1]
is scaled to L[3:3]
, the
empty insertion point at offset 3
). Python slices
are always extracted from left to right, even if you use negative
indexes (they are first converted to positive indexes by adding the
length). Note that Python 2.3 three-limit slices modify this behavior
somewhat: L[3:1:-1]
does extract from right to
left.
>>>L = [1, 2, 3, 4]
>>>L[4]
Traceback (innermost last): File "<stdin>", line 1, in ? IndexError: list index out of range >>>L[-1000:100]
[1, 2, 3, 4] >>>L[3:1]
[ ] >>>L
[1, 2, 3, 4] >>>L[3:1] = ['?']
>>>L
[1, 2, 3, '?', 4]
Indexing, slicing, and del. Your interaction with the interpreter should look something like the following code. Note that assigning an empty list to an offset stores an empty list object there, but assigning an empty list to a slice deletes the slice. Slice assignment expects another sequence, or you’ll get a type error; it inserts items inside the sequence assigned, not the sequence itself:
>>>L = [1,2,3,4]
>>>L[2] = [ ]
>>>L
[1, 2, [ ], 4] >>>L[2:3] = [ ]
>>>L
[1, 2, 4] >>>del L[0]
>>>L
[2, 4] >>>del L[1:]
>>>L
[2] >>>L[1:2] = 1
Traceback (innermost last): File "<stdin>", line 1, in ? TypeError: illegal argument type for built-in operation
Tuple assignment. The values of
X
and Y
are swapped. When
tuples appear on the left and right of an assignment symbol
(=
), Python assigns objects on the right to
targets on the left, according to their positions. This is probably
easiest to understand by noting that targets on the left
aren’t a real tuple, even though they look like one;
they are simply a set of independent assignment targets. The items on
the right are a tuple, which get unpacked during the assignment (the
tuple provides the temporary assignment needed to achieve the swap
effect).
>>>X = 'spam'
>>>Y = 'eggs'
>>>X, Y = Y, X
>>>X
'eggs' >>>Y
'spam'
Dictionary keys. Any immutable object can be used as a dictionary key—integers, tuples, strings, and so on. This really is a dictionary, even though some of its keys look like integer offsets. Mixed type keys work fine too.
>>>D = { }
>>>D[1] = 'a'
>>>D[2] = 'b'
>>>D[(1, 2, 3)] = 'c'
>>>D
{1: 'a', 2: 'b', (1, 2, 3): 'c'}
Dictionary indexing. Indexing a nonexistent key
(D['d']
) raises an error; assigning to a
nonexistent key (D['d']='spam
') creates a new
dictionary entry. On the other hand, out-of-bounds indexing for lists
raises an error too, but so do out-of-bounds assignments. Variable
names work like dictionary keys; they must have already been assigned
when referenced, but are created when first assigned. In fact,
variable names can be processed as dictionary keys if you wish
(they’re made visible in module namespace or
stack-frame dictionaries).
>>>D = {'a':1, 'b':2, 'c':3}
>>>D['a']
1 >>>D['d']
Traceback (innermost last): File "<stdin>", line 1, in ? KeyError: d >>>D['d'] = 4
>>>D
{'b': 2, 'd': 4, 'a': 1, 'c': 3} >>> >>>L = [0,1]
>>>L[2]
Traceback (innermost last): File "<stdin>", line 1, in ? IndexError: list index out of range >>>L[2] = 3
Traceback (innermost last): File "<stdin>", line 1, in ? IndexError: list assignment index out of range
Generic operations. Question answers:
The +
operator doesn’t work on
different/mixed types (e.g., string +
list, list
+
tuple).
+
doesn’t work for dictionaries,
because they aren’t sequences.
The append
method works only for lists, not
strings, and keys
works only on dictionaries.
append
assumes its target is mutable, since
it’s an in-place extension; strings are immutable.
Slicing and concatenation always return a new object of the same type as the objects processed:
>>>"x" + 1
Traceback (innermost last): File "<stdin>", line 1, in ? TypeError: illegal argument type for built-in operation >>> >>>{ } + { }
Traceback (innermost last): File "<stdin>", line 1, in ? TypeError: bad operand type(s) for + >>> >>>[ ].append(9)
>>>"".append('s')
Traceback (innermost last): File "<stdin>", line 1, in ? AttributeError: attribute-less object >>> >>>{ }.keys( )
[ ] >>>[ ].keys( )
Traceback (innermost last): File "<stdin>", line 1, in ? AttributeError: keys >>> >>>[ ][:]
[ ] >>>""[:]
''
String indexing. Since strings are collections
of one-character strings, every time you index a string, you get back
a string, which can be indexed again.
S[0][0][0][0][0]
just keeps indexing the first
character over and over. This generally doesn’t work
for lists (lists can hold arbitrary objects), unless the list
contains strings.
>>>S = "spam"
>>>S[0][0][0][0][0]
's' >>>L = ['s', 'p']
>>>L[0][0][0]
's'
Immutable types. Either of the solutions below work. Index assignment doesn’t, because strings are immutable.
>>>S = "spam"
>>>S = S[0] + 'l' + S[2:]
>>>S
'slam' >>>S = S[0] + 'l' + S[2] + S[3]
>>>S
'slam'
Nesting. Here is a sample:
>>>me = {'name':('mark', 'e', 'lutz'), 'age':'?', 'job':'engineer'}
>>>me['job']
'engineer' >>>me['name'][2]
'lutz'
Files. Here’s one way to create
and read back a text file in Python (ls
is a Unix
command; use dir
on Windows):
#File: maker.py file = open('myfile.txt', 'w') file.write('Hello file world! ') # Or: open( ).write( ) file.close( ) # close not always needed #File: reader.py file = open('myfile.txt', 'r') print file.read( ) # Or print open( ).read( ) %python maker.py
%python reader.py
Hello file world! %ls -l myfile.txt
-rwxrwxrwa 1 0 0 19 Apr 13 16:33 myfile.txt
The dir function revisited:
Here’s what you get for lists; dictionaries do the
same (but with different method names). Note that the
dir
result expanded in Python
2.2—you’ll see a large set of additional
underscore names that implement expression operators, and support the
subclassing in Part VI. The __methods__
attribute disappeared in 2.2 as well, because
it wasn’t consistently implemented—use
dir
to to fetch attribute lists today instead:
>>>[ ].__methods__
['append', 'count', 'index', 'insert', 'remove', 'reverse', 'sort',...] >>>dir([ ])
['append', 'count', 'index', 'insert', 'remove', 'reverse', 'sort',...]
See Section 11.3 for the exercises.
Coding basic loops. As you work through this exercise, you’ll wind up with code that looks like the following:
>>>S = 'spam'
>>>for c in S:
...print ord(c)
... 115 112 97 109 >>>x = 0
>>>for c in S: x = x + ord(c) # Or: x += ord(c)
... >>>x
433 >>>x = [ ]
>>>for c in S: x.append(ord(c))
... >>>x
[115, 112, 97, 109] >>>map(ord, S)
[115, 112, 97, 109]
Backslash characters. The example prints the
bell character (a
) 50 times; assuming your
machine can handle it, and when run outside of IDLE, you may get a
series of beeps (or one long tone, if your machine is fast enough).
Hey—we warned you.
Sorting dictionaries. Here’s
one way to work through this exercise (see Chapter 6 if this doesn’t make sense).
Remember, you really do have to split the keys
and
sort
calls up like this, because sort returns
None
. In Python 2.2, you can iterate through
dictionary keys directly without calling keys
(e.g., for key in D
:), but the keys list will not
be sorted like it is by this code:
>>>D = {'a':1, 'b':2, 'c':3, 'd':4, 'e':5, 'f':6, 'g':7}
>>>D
{'f': 6, 'c': 3, 'a': 1, 'g': 7, 'e': 5, 'd': 4, 'b': 2} >>> >>>keys = D.keys( )
>>>keys.sort( )
>>>for key in keys:
...print key, '=>', D[key]
... a => 1 b => 2 c => 3 d => 4 e => 5 f => 6 g => 7
Program logic alternatives. Here’s sample code for the solutions. Your results may vary a bit; this exercise is mostly designed to get you playing with code alternatives, so anything reasonable gets full credit:
L = [1, 2, 4, 8, 16, 32, 64] X = 5 i = 0 while i < len(L): if 2 ** X == L[i]: print 'at index', i break i = i+1 else: print X, 'not found' L = [1, 2, 4, 8, 16, 32, 64] X = 5 for p in L: if (2 ** X) == p: print (2 ** X), 'was found at', L.index(p) break else: print X, 'not found' L = [1, 2, 4, 8, 16, 32, 64] X = 5 if (2 ** X) in L: print (2 ** X), 'was found at', L.index(2 ** X) else: print X, 'not found' X = 5 L = [ ] for i in range(7): L.append(2 ** i) print L if (2 ** X) in L: print (2 ** X), 'was found at', L.index(2 ** X) else: print X, 'not found' X = 5 L = map(lambda x: 2**x, range(7)) print L if (2 ** X) in L: print (2 ** X), 'was found at', L.index(2 ** X) else: print X, 'not found'
See Section 14.9 for the exercises.
The basics. There’s not much to
this one, but notice that using print
(and hence
your function) is technically a polymorphic
operation, which does the right thing for each type of object:
%python
>>>def func(x): print x
... >>>func("spam")
spam >>>func(42)
42 >>>func([1, 2, 3])
[1, 2, 3] >>>func({'food': 'spam'})
{'food': 'spam'}
Arguments. Here’s a sample
solution. Remember that you have to use print
to
see results in the test calls, because a file isn’t
the same as code typed interactively; Python doesn’t
normally echo the results of expression statements in files.
def adder(x, y):
return x + y
print adder(2, 3)
print adder('spam', 'eggs')
print adder(['a', 'b'], ['c', 'd'])
% python mod.py
5
spameggs
['a', 'b', 'c', 'd']
varargs. Two alternative adder functions are
shown in the following file, adders.py. The hard
part here is figuring out how to initialize an accumulator to an
empty value of whatever type is passed in. The first solution, uses
manual type testing to look for an integer and an empty slice of the
first argument (assumed to be a sequence) otherwise. The second
solution, uses the first argument to initialize and scan items 2 and
beyond, much like one of the min
function variants
shown in Chapter 13.
The second solution is better. Both of these assume all arguments are
the same type and neither works on dictionaries; as we saw in Part II, +
doesn’t work on mixed types or dictionaries. We
could add a type test and special code to add dictionaries too, but
that’s extra credit.
def adder1(*args):
print 'adder1',
if type(args[0]) == type(0): # Integer?
sum = 0 # Init to zero.
else: # else sequence:
sum = args[0][:0] # Use empty slice of arg1.
for arg in args:
sum = sum + arg
return sum
def adder2(*args):
print 'adder2',
sum = args[0] # Init to arg1.
for next in args[1:]:
sum = sum + next # Add items 2..N.
return sum
for func in (adder1, adder2):
print func(2, 3, 4)
print func('spam', 'eggs', 'toast')
print func(['a', 'b'], ['c', 'd'], ['e', 'f'])
% python adders.py
adder1 9
adder1 spameggstoast
adder1 ['a', 'b', 'c', 'd', 'e', 'f']
adder2 9
adder2 spameggstoast
adder2 ['a', 'b', 'c', 'd', 'e', 'f']
Keywords. Here is our solution to the first part
of this exercise (file mod.py). To iterate over
keyword arguments, use a **args
form in the
function header and use a loop like: for x in args.keys( ):
use args[x]
.
def adder(good=1, bad=2, ugly=3):
return good + bad + ugly
print adder( )
print adder(5)
print adder(5, 6)
print adder(5, 6, 7)
print adder(ugly=7, good=6, bad=5)
% python mod.py
6
10
14
18
18
and 6. Here are our solutions to exercises 5 and 6 (file
dicts.py). These are just coding exercises,
though, because Python 1.5 added dictionary methods, to do things
like copying and adding (merging) dictionaries: D.copy(
)
, and D1.update(D2)
. (See
Python’s library manual or the Python
Pocket Reference for more details).
X[:]
doesn’t work for
dictionaries, since they’re not sequences (see Chapter 6 for details). Also remember that if we assign
(e = d
) rather than copy, we generate a reference
to a shared dictionary object; changing
d
changes e
, too.
def copyDict(old): new = { } for key in old.keys( ): new[key] = old[key] return new def addDict(d1, d2): new = { } for key in d1.keys( ): new[key] = d1[key] for key in d2.keys( ): new[key] = d2[key] return new %python
>>>from dicts import *
>>>d = {1:1, 2:2}
>>>e = copyDict(d)
>>>d[2] = '?'
>>>d
{1: 1, 2: '?'} >>>e
{1: 1, 2: 2} >>>x = {1:1}
>>>y = {2:2}
>>>z = addDict(x, y)
>>>z
{1: 1, 2: 2}
More argument matching examples. Here is the sort of interaction you should get, along with comments that explain the matching that goes on:
def f1(a, b): print a, b # Normal args def f2(a, *b): print a, b # Positional varargs def f3(a, **b): print a, b # Keyword varargs def f4(a, *b, **c): print a, b, c # Mixed modes def f5(a, b=2, c=3): print a, b, c # Defaults def f6(a, b=2, *c): print a, b, c # Defaults and positional varargs %python
>>>f1(1, 2)
# Matched by position (order matters) 1 2 >>>f1(b=2, a=1)
# Matched by name (order doesn't matter) 1 2 >>>f2(1, 2, 3)
# Extra positionals collected in a tuple 1 (2, 3) >>>f3(1, x=2, y=3)
# Extra keywords collected in a dictionary 1 {'x': 2, 'y': 3} >>>f4(1, 2, 3, x=2, y=3)
# Extra of both kinds 1 (2, 3) {'x': 2, 'y': 3} >>>f5(1)
# Both defaults kick in. 1 2 3 >>>f5(1, 4)
# Only one default used 1 4 3 >>>f6(1)
# One argument: matches "a" 1 2 ( ) >>>f6(1, 3, 4)
# Extra positional collected 1 3 (4,)
Primes revisited. Below is the primes example
wrapped up in a function and module (file
primes.py) so it can be run multiple times. We
added an if
test to trap negatives, 0, and 1. We
also changed /
to //
to make
this immune from the Python 3.0 /
“true” division changes we studied
in Chapter 4, and support floating-point numbers.
The //
operator works in both the current and
future division scheme, but the future /
operator
fails (uncomment the from
and change
//
to /
to see the differences
in 2.2 and 3.0).
#from __future__ import division def prime(y): if y <= 1: # For some y > 1 print y, 'not prime' else: x = y // 2 # Future / fails while x > 1: if y % x == 0: # No remainder? print y, 'has factor', x break # Skip else. x -= 1 else: print y, 'is prime' prime(13); prime(13.0) prime(15); prime(15.0) prime(3); prime(2) prime(1); prime(-3)
Here is the module in action; the //
operator
allows it to works for floating-point numbers too, even though it
perhaps should not:
% python primes.py
13 is prime
13.0 is prime
15 has factor 5
15.0 has factor 5.0
3 is prime
2 is prime
1 not prime
-3 not prime
This function still isn’t very reusable yet—it
could return values instead of printing—but
it’s enough to run experiments.
It’s also still not a strict mathematical prime
(floating-points work), and is still inefficient. Improvements are
left as exercises for more mathematically-minded readers. Hint: a
for
loop over range(y, 1, -1)
may be a bit quicker than the while
(in fact,
it’s roughly twice as fast in 2.2), but the
algorithm is the real bottleneck here. To time alternatives, use the
built-in time
module, and coding patterns like
those used in this general function-call timer (see the library
manual for details):
def timer(reps, func, *args): import time start = time.clock( ) for i in xrange(reps): apply(func, args) return time.clock( ) - start
List comprehensions. Here is the sort of code you should write; we may have a preference, but we’re not telling:
>>> values = [2, 4, 9, 16, 25] >>> import math >>> res = [ ] >>> for x in values: res.append(math.sqrt(x)) ... >>> res [1.4142135623730951, 2.0, 3.0, 4.0, 5.0] >>> map(math.sqrt, values) [1.4142135623730951, 2.0, 3.0, 4.0, 5.0] >>> [math.sqrt(x) for x in values] [1.4142135623730951, 2.0, 3.0, 4.0, 5.0]
See Section 18.8 for the exercises.
Basics, import. This one is simpler than you may
think. When you’re done, your file and interaction
should look close to the following code (file
mymod.py); remember that Python can read a whole
file into a string or lines list, and the len
built-in returns the length of strings and lists:
def countLines(name): file = open(name, 'r') return len(file.readlines( )) def countChars(name): return len(open(name, 'r').read( )) def test(name): # Or pass file object return countLines(name), countChars(name) # Or return a dictionary %python
>>>import mymod
>>>mymod.test('mymod.py')
(10, 291)
On Unix, you can verify your output with a wc command; on Windows,
right-click on your file to views its properties. But note that your
script may report fewer characters than Windows does—for
portability, Python converts Windows
line-end
markers to
, thereby dropping one byte
(character) per line. To match byte counts with Windows exactly, you
have to open in binary mode (rb
) or add back the
number of lines.
Incidentally, to do the “ambitious”
part (passing in a file object, so you only open the file once),
you’ll probably need to use the
seek
method of the built-in file object. We
didn’t cover it in the text, but it works just like
C’s fseek
call (and calls it
behind the scenes): seek
resets the current
position in the file to an offset passed in. After a
seek
, future input/output operations are relative
to the new position. To rewind to the start of a file without closing
and reopening, call file.seek(0)
; the file
read
methods all pick up at the current position
in the file, so you need to rewind to reread. Here’s
what this tweak would look like:
def countLines(file): file.seek(0) # Rewind to start of file. return len(file.readlines( )) def countChars(file): file.seek(0) # Ditto (rewind if needed) return len(file.read( )) def test(name): file = open(name, 'r') # Pass file object. return countLines(file), countChars(file) # Open file only once. >>>import mymod2
>>>mymod2.test("mymod2.py")
(11, 392)
from/from*. Here’s the
from*
part. Replace *
with
countChars
to do the rest.
%python
>>>from mymod import *
>>>countChars("mymod.py")
291
__main__
. If you code it properly, it works in
either mode (program run or module import):
def countLines(name):
file = open(name, 'r')
return len(file.readlines( ))
def countChars(name):
return len(open(name, 'r').read( ))
def test(name): # Or pass file object
return countLines(name), countChars(name) # Or return a dictionary
if __name__ == '__main__':
print test('mymod.py')
% python mymod.py
(13, 346)
Nested imports. Here is our solution (file myclient.py):
from mymod import countLines, countChars
print countLines('mymod.py'), countChars('mymod.py')
% python myclient.py
13 346
As for the rest of this one:
mymod
’s functions are accessible
(that is, importable) from the top level of
myclient
, since from
simply
assigns to names in the importer (it works almost as though
mymod
’s defs appeared in
myclient
). For example, another file can say this:
import myclient myclient.countLines(...) from myclient import countChars countChars(...)
If myclient
used import
instead
of from
, you’d need to use a path
to get to the functions in mymod
through
myclient
:
import myclient myclient.mymod.countLines(...) from myclient import mymod mymod.countChars(...)
In general, you can define collector modules
that import all the names from other modules, so
they’re available in a single convenience module.
Using the following code, you wind up with three different copies of
name somename
: mod1.somename
,
collector.somename
, and __main__.somename
; all three share the same integer object
initially, and only the name somename
exists at
the interactive prompt as is:
#File: mod1.py
somename = 42
#File: collector.py
from mod1 import * # Collect lots of names here.
from mod2 import * # from assigns to my names.
from mod3 import *
>>> from collector import somename
Package imports. For this, we put the
mymod.py solution file listed for exercise 3
into a directory package. The following is what we did to set up the
directory and its required __init__.py file in
a Windows console interface; you’ll need to
interpolate for other platforms (e.g., use mv
and
vi
instead of move
and
edit
). This works in any directory (we just
happened to run our commands in Python’s install
directory), and you can do some of this from a file explorer GUI,
too.
When we were done, we had a mypkg subdirectory,
which contained files __init__.py and
mymod.py. You need an __init__.py in the mypkg directory, but not
in its parent; mypkg is located in the home
directory component of the module search path. Notice how a
print
statement coded in the
directory’s initialization file only fires the first
time it is imported, not the second:
C:python22>mkdir mypkg
C:Python22>move mymod.py mypkgmymod.py
C:Python22>edit mypkg\__init__.py
...coded a print statement... C:Python22>python
>>import mypkg.mymod
initializing mypkg >>>mypkg.mymod.countLines('mypkgmymod.py')
13 >>>from mypkg.mymod import countChars
>>>countChars('mypkgmymod.py')
346
Reload. This exercise just asks you to experiment with changing the changer.py example in the book, so there’s nothing to show here.
Circular imports. The short story is that
importing recur2
first works, because the
recursive import then happens at the import in
recur1
, not at a from
in
recur2
.
The long story goes like this: importing recur2
first works, because the recursive import from
recur1
to recur2
fetches
recur2
as a whole, instead of getting specific
names. recur2
is incomplete when imported from
recur1
, but because it uses
import
instead of from
,
you’re safe: Python finds and returns the already
created recur2
module object and continues to run
the rest of recur1
without a glitch. When the
recur2
import resumes, the second
from
finds name Y
in
recur1
(it’s been run
completely), so no error is reported. Running a file as a script is
not the same as importing it as a module; these cases are the same as
running the first import
or
from
in the script interactively. For instance,
running recur1
as a script is the same as
importing recur2
interactively, since
recur2
is the first module imported in
recur1
.
See Section 23.5 for the exercises.
Inheritance. Here’s the
solution code for this exercise (file adder.py),
along with some interactive tests. The __add__
overload has to appear only once, in the superclass, since it invokes
type-specific add
methods in subclasses.
class Adder: def add(self, x, y): print 'not implemented!' def __init__(self, start=[ ]): self.data = start def __add__(self, other): # Or in subclasses? return self.add(self.data, other) # Or return type? class ListAdder(Adder): def add(self, x, y): return x + y class DictAdder(Adder): def add(self, x, y): new = { } for k in x.keys( ): new[k] = x[k] for k in y.keys( ): new[k] = y[k] return new %python
>>>from adder import *
>>>x = Adder( )
>>>x.add(1, 2)
not implemented! >>>x = ListAdder( )
>>>x.add([1], [2])
[1, 2] >>>x = DictAdder( )
>>>x.add({1:1}, {2:2})
{1: 1, 2: 2} >>>x = Adder([1])
>>>x + [2]
not implemented! >>> >>>x = ListAdder([1])
>>>x + [2]
[1, 2] >>>[2] + x
Traceback (innermost last): File "<stdin>", line 1, in ? TypeError: __add__ nor __radd__ defined for these operands
Notice in the last test that you get an error for expressions where a
class instance appears on the right of a +
; if you
want to fix this, use __radd__
methods as
described in Section 21.4 in Chapter 21.
If you are saving a value in the instance anyhow, you might as well
rewrite the add
method to take just one argument,
in the spirit of other examples in Part VI:
class Adder: def __init__(self, start=[ ]): self.data = start def __add__(self, other): # Pass a single argument. return self.add(other) # The left side is in self. def add(self, y): print 'not implemented!' class ListAdder(Adder): def add(self, y): return self.data + y class DictAdder(Adder): def add(self, y): pass # Change me to use self.data instead of x. x = ListAdder([1,2,3]) y = x + [4,5,6] print y # Prints [1, 2, 3, 4, 5, 6]
Because values are attached to objects rather than passed around,
this version is arguably more object-oriented. And once
you’ve gotten to this point, you’ll
probably see that you could get rid of add
altogether, and simply define type-specific __add__
methods in the two subclasses.
Operator overloading. The solution code (file
mylist.py) uses a few operator overload methods
we didn’t say much about, but they should be
straightforward to understand. Copying the initial value in the
constructor is important, because it may be mutable; you
don’t want to change or have a reference to an
object that’s possibly shared somewhere outside the
class. The __getattr__
method routes calls to
the wrapped list. For hints on an easier way to code this as of
Python 2.2, see Section 23.1.2 in Chapter 23.
class MyList:
def __init__(self, start):
#self.wrapped = start[:] # Copy start: no side effects
self.wrapped = [ ] # Make sure it's a list here.
for x in start: self.wrapped.append(x)
def __add__(self, other):
return MyList(self.wrapped + other)
def __mul__(self, time):
return MyList(self.wrapped * time)
def __getitem__(self, offset):
return self.wrapped[offset]
def __len__(self):
return len(self.wrapped)
def __getslice__(self, low, high):
return MyList(self.wrapped[low:high])
def append(self, node):
self.wrapped.append(node)
def __getattr__(self, name): # Other members: sort/reverse/etc
return getattr(self.wrapped, name)
def __repr__(self):
return `self.wrapped`
if __name__ == '__main__':
x = MyList('spam')
print x
print x[2]
print x[1:]
print x + ['eggs']
print x * 3
x.append('a')
x.sort( )
for c in x: print c,
% python mylist.py
['s', 'p', 'a', 'm']
a
['p', 'a', 'm']
['s', 'p', 'a', 'm', 'eggs']
['s', 'p', 'a', 'm', 's', 'p', 'a', 'm', 's', 'p', 'a', 'm']
a a m p s
Note that it’s important to copy the start value by appending instead of slicing here, because the result may other wise not be a true list, and so would not respond to expected list methods such as append (e.g., slicing a string returns another string, not a list). You would be able to copy a MyList start value by slicing, because its class overloads the slicing operation and provides the expected list interface. You need to avoid sliced-based copying for things such as strings, however.
Subclassing. Our solution (mysub.py) appears below. Your solution should be similar.
from mylist import MyList
class MyListSub(MyList):
calls = 0 # Shared by instances
def __init__(self, start):
self.adds = 0 # Varies in each instance
MyList.__init__(self, start)
def __add__(self, other):
MyListSub.calls = MyListSub.calls + 1 # Class-wide counter
self.adds = self.adds + 1 # Per instance counts
return MyList.__add__(self, other)
def stats(self):
return self.calls, self.adds # All adds, my adds
if __name__ == '__main__':
x = MyListSub('spam')
y = MyListSub('foo')
print x[2]
print x[1:]
print x + ['eggs']
print x + ['toast']
print y + ['bar']
print x.stats( )
% python mysub.py
a
['p', 'a', 'm']
['s', 'p', 'a', 'm', 'eggs']
['s', 'p', 'a', 'm', 'toast']
['f', 'o', 'o', 'bar']
(3, 2)
Metaclass methods. We worked through this
exercise as follows. Notice that operators try to fetch attributes
through __getattr__
too; you need to return a
value to make them work.
>>>class Meta:
...def __getattr__(self, name):
...print 'get', name
...def __setattr__(self, name, value):
...print 'set', name, value
... >>>x = Meta( )
>>>x.append
get append >>>x.spam = "pork"
set spam pork >>> >>>x + 2
get __coerce__ Traceback (innermost last): File "<stdin>", line 1, in ? TypeError: call of non-function >>> >>>x[1]
get __getitem__ Traceback (innermost last): File "<stdin>", line 1, in ? TypeError: call of non-function >>>x[1:5]
get __len__ Traceback (innermost last): File "<stdin>", line 1, in ? TypeError: call of non-function
Set objects. Here’s the sort of interaction you should get. Comments explain which methods are called.
%python
>>>from setwrapper import Set
>>>x = Set([1,2,3,4])
# Runs __init__ >>>y = Set([3,4,5])
>>>x & y
# __and__, intersect, then __repr__ Set:[3, 4] >>>x | y
# __or__, union, then __repr__ Set:[1, 2, 3, 4, 5] >>>z = Set("hello")
# __init__ removes duplicates. >>>z[0], z[-1]
# __getitem__ ('h', 'o') >>>for c in z: print c,
# __getitem__ ... h e l o >>>len(z), z
# __len__, __repr__ (4, Set:['h', 'e', 'l', 'o']) >>>z & "mello", z | "mello"
(Set:['e', 'l', 'o'], Set:['h', 'e', 'l', 'o', 'm'])
Our solution to the multiple-operand extension subclass looks like the class below (file multiset.py). It only needs to replace two methods in the original set. The class’s documentation string explains how it works.
from setwrapper import Set class MultiSet(Set): """ inherits all Set names, but extends intersect and union to support multiple operands; note that "self" is still the first argument (stored in the *args argument now); also note that the inherited & and | operators call the new methods here with 2 arguments, but processing more than 2 requires a method call, not an expression: """ def intersect(self, *others): res = [ ] for x in self: # Scan first sequence for other in others: # for all other args. if x not in other: break # Item in each one? else: # No: break out of loop res.append(x) # Yes: add item to end return Set(res) def union(*args): # self is args[0]. res = [ ] for seq in args: # For all args for x in seq: # For all nodes if not x in res: res.append(x) # Add new items to result. return Set(res)
Your interaction with the extension will be something along the
following lines. Note that you can intersect by using
&
or calling intersect
, but
must call intersect
for three or more operands;
&
is a binary (two-sided) operator. Also note
that we could have called MutiSet
simply
Set
to make this change more transparent if we
used setwrapper.Set
to refer to the original
within multiset
:
>>>from multiset import *
>>>x = MultiSet([1,2,3,4])
>>>y = MultiSet([3,4,5])
>>>z = MultiSet([0,1,2])
>>>x & y, x | y
# Two operands (Set:[3, 4], Set:[1, 2, 3, 4, 5]) >>>x.intersect(y, z)
# Three operands Set:[ ] >>>x.union(y, z)
Set:[1, 2, 3, 4, 5, 0] >>>x.intersect([1,2,3], [2,3,4], [1,2,3])
# Four operands Set:[2, 3] >>>x.union(range(10))
# non-MultiSets work too. Set:[1, 2, 3, 4, 0, 5, 6, 7, 8, 9]
Class tree links. Below is the way we changed
the Lister
class, and a rerun of the test to show
its format. To display inherited class attributes too,
you’d need to do something like what the
attrnames
method currently does, but recursively,
at each class reached by climbing __bases__
links. Because dir
includes inherited attributes
in Python 2.2, you might also simply loop through its result: say
for x
in dir(self)
and use
getattr(self,x)
. This won’t
directly help, if you wish to represent the class
tree’s structure in your display like the
classtree.py example in Chapter 21.
class Lister: def __repr__(self): return ("<Instance of %s(%s), address %s: %s>" % (self.__class__.__name__, # My class's name self.supers( ), # My class's supers id(self), # My address self.attrnames( )) ) # name=value list def attrnames(self): ...unchanged
... def supers(self): result = "" first = 1 for super in self.__class__.__bases__: # One level up from class if not first: result = result + ", " first = 0 result = result + super.__name__ # name, not repr(super) return result C:pythonexamples>python testmixin.py
<Instance of Sub(Super, Lister), address 7841200: name data3=42 name data2=eggs name data1=spam >
Composition. Our solution is below (file lunch.py), with comments from the description mixed in with the code. This is one case where it’s probably easier to express a problem in Python than it is in English.
class Lunch:
def __init__(self): # Make/embed Customer and Employee.
self.cust = Customer( )
self.empl = Employee( )
def order(self, foodName): # Start a Customer order simulation.
self.cust.placeOrder(foodName, self.empl)
def result(self): # Ask the Customer about its Food.
self.cust.printFood( )
class Customer:
def __init__(self): # Initialize my food to None.
self.food = None
def placeOrder(self, foodName, employee): # Place order with Employee.
self.food = employee.takeOrder(foodName)
def printFood(self): # Print the name of my food.
print self.food.name
class Employee:
def takeOrder(self, foodName): # Return a Food, with requested name.
return Food(foodName)
class Food:
def __init__(self, name): # Store food name.
self.name = name
if __name__ == '__main__':
x = Lunch( ) # Self-test code
x.order('burritos') # If run, not imported
x.result( )
x.order('pizza')
x.result( )
% python lunch.py
burritos
pizza
Zoo Animal Hierarchy. Here is the way we coded
the taxonomy on Python (file zoo.py);
it’s artificial, but the general coding pattern
applies to many real structures—from GUIs to employee
databases. Notice that the self.speak
reference in
Animal
triggers an independent inheritance search,
which finds speak
in a subclass. Test this
interactively per the exercise description. Try extending this
hierarchy with new classes, and making instances of various classes
in the tree.
class Animal: def reply(self): self.speak( ) # Back to subclass def speak(self): print 'spam' # Custom message class Mammal(Animal): def speak(self): print 'huh?' class Cat(Mammal): def speak(self): print 'meow' class Dog(Mammal): def speak(self): print 'bark' class Primate(Mammal): def speak(self): print 'Hello world!' class Hacker(Primate): pass # Inherit from Primate.
The Dead Parrot Sketch. Here’s
how we implemented this one (file parrot.py).
Notice how the line
method in the
Actor
superclass works: by accessing
self
attributes twice, it sends Python back to the
instance twice, and hence invokes two
inheritance searches—self.name
and
self.says( )
find information in the specific
subclasses.
class Actor: def line(self): print self.name + ':', `self.says( )` class Customer(Actor): name = 'customer' def says(self): return "that's one ex-bird!" class Clerk(Actor): name = 'clerk' def says(self): return "no it isn't..." class Parrot(Actor): name = 'parrot' def says(self): return None class Scene: def __init__(self): self.clerk = Clerk( ) # Embed some instances. self.customer = Customer( ) # Scene is a composite. self.subject = Parrot( ) def action(self): self.customer.line( ) # Delegate to embedded. self.clerk.line( ) self.subject.line( )
See Section 26.6 for the exercises.
try/except. Our version of the
oops
function (file oops.py)
follows. As for the noncoding questions, changing
oops
to raise KeyError
instead
of IndexError
means that the exception
won’t be caught by the try
handler (it “percolates” to the top
level and triggers Python’s default error message).
The names KeyError
and
IndexError
come from the outermost built-in names
scope. Import __builtin__
and pass it as an
argument to the dir
function to see for yourself.
def oops( ):
raise IndexError
def doomed( ):
try:
oops( )
except IndexError:
print 'caught an index error!'
else:
print 'no error caught...'
if __name__ == '__main__': doomed( )
% python oops.py
caught an index error!
Exception objects and lists. Here’s the way we extended this module for an exception of our own (here a string, at first):
MyError = 'hello'
def oops( ):
raise MyError, 'world'
def doomed( ):
try:
oops( )
except IndexError:
print 'caught an index error!'
except MyError, data:
print 'caught error:', MyError, data
else:
print 'no error caught...'
if __name__ == '__main__':
doomed( )
% python oops.py
caught error: hello world
To identify the exception with a class, we just changed the first part of the file to this, and saved it as oop_oops.py:
class MyError: pass def oops( ): raise MyError( ) ...rest unchanged...
Like all class exceptions, the instance comes back as the extra data;
our error message now shows both the class, and its instance
(<...>
).
% python oop_oops.py
caught error: __main__.MyError <__main__.MyError instance at 0x00867550>
Remember, to make this look nicer, you can define a __repr__
or __str__
method in your class to
return a custom print string. See Chapter 21 for
details.
Error handling. Here’s one way to solve this one (file safe2.py). We did our tests in a file, rather than interactively, but the results are about the same.
import sys, traceback
def safe(entry, *args):
try:
apply(entry, args) # catch everything else
except:
traceback.print_exc( )
print 'Got', sys.exc_type, sys.exc_value
import oops
safe(oops.oops)
% python safe2.py
Traceback (innermost last):
File "safe2.py", line 5, in safe
apply(entry, args) # catch everything else
File "oops.py", line 4, in oops
raise MyError, 'world'
hello: world
Got hello world
See Section 27.9 for the exercises.
Avoiding regular expressions. This program is
long and tedious, but not especially complicated. See if you can
understand how it works. Whether this is easier for you than regular
expressions depends on many factors, such as your familiarity with
regular expressions and your comfort with the functions in the
string
module. Use whichever type of programming
works for you.
file = open('pepper.txt') text = file.read( ) paragraphs = text.split(' ') def find_indices_for(big, small): indices = [ ] cum = 0 while 1: index = big.find(small) if index == -1: return indices indices.append(index+cum) big = big[index+len(small):] cum = cum + index + len(small) def fix_paragraphs_with_word(paragraphs, word): lenword = len(word) for par_no in range(len(paragraphs)): p = paragraphs[par_no] wordpositions = find_indices_for(p, word) if wordpositions == [ ]: return for start in wordpositions: # Look for 'pepper' ahead. indexpepper = p.find('pepper') if indexpepper == -1: return -1 if p[start:indexpepper].strip( ): # Something other than whitespace in between! continue where = indexpepper+len('pepper') if p[where:where+len('corn')] == 'corn': # It's immediately followed by 'corn'! continue if p.find('salad') < where: # It's not followed by 'salad'. continue # Finally! We get to do a change! p = p[:start] + 'bell' + p[start+lenword:] paragraphs[par_no] = p # Change mutable argument! fix_paragraphs_with_word(paragraphs, 'red') fix_paragraphs_with_word(paragraphs, 'green') for paragraph in paragraphs: print paragraph+' '
We won’t repeat the output here; it’s the same as that of the regular expression solution.
Wrapping a text file with a class. This one is
surprisingly easy, if you understand classes and the
split
function in the string
module. The following is a version that has one little twist over and
beyond what we asked for:
class FileStrings: def __init__(self, filename=None, data=None): if data == None: self.data = open(filename).read( ) else: self.data = data self.paragraphs = self.data.split(' ') self.lines = self.data.split(' ') self.words = self.data.split( ) def __repr__(self): return self.data def paragraph(self, index): return FileStrings(data=self.paragraphs[index]) def line(self, index): return FileStrings(data=self.lines[index]) def word(self, index): return self.words[index]
This solution, when applied to the file pepper.txt, gives:
>>>from FileStrings import FileStrings
>>>bigtext = FileStrings('pepper.txt')
>>>print bigtext.paragraph(0)
This is a paragraph that mentions bell peppers multiple times. For one, here is a red Pepper and dried tomato salad recipe. I don't like to use green peppers in my salads as much because they have a harsher flavor. >>>print bigtext.line(0)
This is a paragraph that mentions bell peppers multiple times. For >>>print bigtext.line(-4)
aren't peppers, they're chilies, but would you rather have a good cook >>>print bigtext.word(-4)
botanist
How does it work? The constructor simply reads all the file into a
big string (the instance attribute data
) and then
splits it according to the various criteria, keeping the results of
the splits in instance attributes that are lists of strings. When
returning from one of the accessor methods, the data itself is
wrapped in a FileStrings
object. This
isn’t required by the assignment, but
it’s nice because it means you can chain the
operations, so that to find out what the last word of the third line
of the third paragraph is, you can just write:
>>> print bigtext.paragraph(2).line(2).word(-1)
'cook'
Describing a directory. There are several solutions to this exercise, naturally. One simple solution is:
import os, sys, stat def describedir(start): def describedir_helper(arg, dirname, files): """ Helper function for describing directories """ print "Directory %s has files:" % dirname for file in files: # Find the full path to the file (directory + filename). fullname = os.path.join(dirname, file) if os.path.isdir(fullname): # If it's a directory, say so; no need to find the size. print ' '+ file + ' (subdir)' else: # Find out the size and print the info. size = os.stat(fullname)[stat.ST_SIZE] print ' '+file+' size=' + `size` # Start the 'walk'. os.path.walk(start, describedir_helper, None)
which uses the walk
function in the
os.path
module, and works just fine:
>>>import describedir
>>>describedir.describedir2('testdir')
Directory testdir has files: describedir.py size=939 subdir1 (subdir) subdir2 (subdir) Directory testdirsubdir1 has files: makezeros.py size=125 subdir3 (subdir) Directory testdirsubdir1subdir3 has files: Directory testdirsubdir2 has files:
Note that you could have found the size of the files by doing
len(open(fullname, 'rb').read( ))
, but this works
only when you have read access to all the files and is quite
inefficient. The stat
call in the
os
module gives out all kinds of useful
information in a tuple, and the stat
module
defines some names that make it unnecessary to remember the order of
the elements in that tuple. See the Library
Reference for details.
Modifying the prompt. The key to this exercise
is to remember that the ps1
and
ps2
attributes of the sys
module can be anything, including a class instance with a __repr
__ or __str
__ method.
For example:
import sys, os class MyPrompt: def __init__(self, subprompt='>>> '): self.lineno = 0 self.subprompt = subprompt def __repr__(self): self.lineno = self.lineno + 1 return os.getcwd( )+'|%d'%(self.lineno)+self.subprompt sys.ps1 = MyPrompt( ) sys.ps2 = MyPrompt('... ')
This code works as shown (use the -i
option of the
Python interpreter to make sure your program starts right away):
h:Davidook>python -i modifyprompt.py
h:Davidook|1>>>x = 3
h:Davidook|2>>>y = 3
h:Davidook|3>>>def foo( ):
h:Davidook|3...x = 3
# The secondary prompt is supported. h:Davidook|3... h:Davidook|4>>>import os
h:Davidook|5>>>os.chdir('..')
h:David|6>>> # Note that the prompt changed!
Writing a simple shell. Mostly, the following
script, which implements the Unix set of commands (well, some of
them) should be self-explanatory. Note that we’ve
only put a “help” message for the
ls
command, but there should be one for all the
other commands as well:
import cmd, os, sys, shutil class UnixShell(cmd.Cmd): def do_EOF(self, line): """ The do_EOF command is called when the user presses Ctrl-D (unix) or Ctrl-Z (PC). """ sys.exit( ) def help_ls(self): print "ls <directory>: list the contents of the specified directory" print " (current directory used by default)" def do_ls(self, line): # 'ls' by itself means 'list current directory' if line == '': dirs = [os.curdir] else: dirs = line.split( ) for dirname in dirs: print 'Listing of %s:' % dirname print ' '.join(os.listdir(dirname) def do_cd(self, dirname): # 'cd' by itself means 'go home'. if dirname == '': dirname = os.environ['HOME'] os.chdir(dirname) def do_mkdir(self, dirname): os.mkdir(dirname) def do_cp(self, line): words = line.split( ) sourcefiles,target = words[:-1], words[-1] # target could be a dir for sourcefile in sourcefiles: shutil.copy(sourcefile, target) def do_mv(self, line): source, target = line.split( ) os.rename(source, target) def do_rm(self, line): [os.remove(arg) for arg in line.split( )] class DirectoryPrompt: def __repr__(self): return os.getcwd( )+'> ' cmd.PROMPT = DirectoryPrompt( ) shell = UnixShell( ) shell.cmdloop( )
Note that we’ve reused the same trick as in exercise
5 of Chapter 8 to have a prompt that adjusts
with the current directory, combined with the trick of modifying the
attribute PROMPT
in the cmd
module itself. Of course those weren’t part of the
assignment, but it’s hard to just limit oneself to a
simple thing when a full-featured one will do. It works, too!
h:Davidook>python -i shell.py
h:Davidook>cd ../tmp
h:David mp>ls
Listing of .: api ERREUR.DOC ext giant_~1.jpg icons index.html lib pythlp.hhc pythlp.hhk ref tut h:David mp>cd ..
h:David>cd tmp
h:David mp>cp index.html backup.html
h:David mp>rm backup.html
h:David mp>^Z
Of course, to be truly useful, this script needs a lot of error checking and many more features, all of which is left, as math textbooks say, as an exercise for the reader.
Redirecting stdout. This is simple: all you have to do is to replace the first line with:
import fileinput, sys # No change here sys.stdout = open(sys.argv[-1], 'w') # Open the output file. del sys.argv[-1] # We've dealt with this argument. ... # Continue as before.
See Section 28.5 for the exercises.
Faking the Web. What you need to do is to create instances of a class that has the fieldnames attribute and appropriate instance variables. One possible solution is:
class FormData: def __init__(self, dict): for k, v in dict.items( ): setattr(self, k, v) class FeedbackData(FormData): """ A FormData generated by the comment.html form. """ fieldnames = ('name', 'address', 'email', 'type', 'text') def __repr__(self): return "%(type)s from %(name)s on %(time)s" % vars(self) fake_entries = [ {'name': "John Doe", 'address': '500 Main St., SF CA 94133', 'email': '[email protected]', 'type': 'comment', 'text': 'Great toothpaste!'}, {'name': "Suzy Doe", 'address': '500 Main St., SF CA 94133', 'email': '[email protected]', 'type': 'complaint', 'text': "It doesn't taste good when I kiss John!"}, ] DIRECTORY = r'C:complaintdir' if __name__ == '__main__': import tempfile, pickle, time tempfile.tempdir = DIRECTORY for fake_entry in fake_entries: data = FeedbackData(fake_entry) filename = tempfile.mktemp( ) data.time = time.asctime(time.localtime(time.time( ))) pickle.dump(data, open(filename, 'w'))
As you can see, the only thing you really had to change was the way
the constructor for FormData
works, since it has
to do the setting of attributes from a dictionary as opposed to a
FieldStorage
object.
Cleaning up. There are many ways to deal with
this problem. One easy one is to modify the
formletter.py program to keep a list of the
filenames that it has already processed (in a pickled file, of
course!). This can be done by modifying the if
__main
__ ==
'__name
__' test to read
something like this (new lines are in bold):
if __name__ == '__main__': import os, pickleCACHEFILE = 'C:cache.pik'
from feedback import DIRECTORY#, FormData, FeedbackDataif os.path.exists(CACHEFILE):
processed_files = pickle.load(open(CACHEFILE))
else:
processed_files = [ ]
for filename in os.listdir(DIRECTORY):if filename in processed_files: continue
# Skip this filename.processed_files.append(filename)
data = pickle.load(open(os.path.join(DIRECTORY, filename))) if data.type == 'complaint': print "Printing letter for %(name)s." % vars(data) print_formletter(data) else: print "Got comment from %(name)s, skipping printing." % vars(data)pickle.dump(processed_file, open(CACHEFILE, 'w')
As you can tell, you simply load a list of the previous filenames if it exists (and use an empty list otherwise) and compare the filenames with entries in the list to determine which to skip. If you don’t skip one, it needs to be added to the list. Finally, at program exit, pickle the new list.
Adding parametric plotting to grapher.py. This
exercise is quite simple, as all that’s needed is to
change the drawing code in the Chart class. Specifically, the code
between xmin, xmax = 0, N-1
and
graphics.fillPolygon(...)
should be placed in an
if
test, so that the new code reads:
if not hasattr(self.data[0], '__len__'): # It's probably a number (1D). xmin, xmax = 0, N-1 # Code from existing program, up to graphics.fillPolygon(xs, ys, len(xs)) elif len(self.data[0]) == 2: # we'll only deal with 2-D xmin = reduce(min, map(lambda d: d[0], self.data)) xmax = reduce(max, map(lambda d: d[0], self.data)) ymin = reduce(min, map(lambda d: d[1], self.data)) ymax = reduce(max, map(lambda d: d[1], self.data)) zero_y = y_offset - int(-ymin/(ymax-ymin)*height) zero_x = x_offset + int(-xmin/(xmax-xmin)*width) for i in range(N): xs[i] = x_offset + int((self.data[i][0]-xmin)/(xmax-xmin)*width) ys[i] = y_offset - int((self.data[i][1]-ymin)/(ymax-ymin)*height) graphics.color = self.color if self.style == "Line": graphics.drawPolyline(xs, ys, len(xs)) else: xs.append(xs[0]); ys.append(ys[0]) graphics.fillPolygon(xs, ys, len(xs))