Friday, January 8, 2010

Ch24



















   







Page
445






















     
 
  Hour 24

Miscellany
 
 






















 
 
  This dog just
ran a thousand miles.

Lynda Plettner, musher, talking about her lead dog RG, at the
end of the
1999 Iditarod.
 
 






















   
 
  In the preceding
hour, we talked about Mandelbrot and Julia sets and their mathematics,
and we discussed a program to calculate and display them. This hour,
which is mostly miscellany, starts off with a brief introduction to Web
CGI programming. Then we talk about right practice, right attitude, and
right understanding.
 
 






















   
 
  CGI  
 






















   
 
  As you know from
previous chapters, CGI stands for common gateway interface. The
programming paradigm for Web CGI programming is a little different from
normal, interactive programs. With CGI, you put your Python programs
into a specific location and then access the program with your Web
browser; the program emits output in a language called HTML, or

Hypertext

Markup

Language.
Various embellishments exist to HTML, such as those that allow users to
fill out forms and submit the information back to
 
 


 




 


 
















   







Page
446






















   
 
  the same, or to
a different, program. A full explanation of Web servers, Web browsers,
HTML, and CGI programming is far beyond the scope of this book; see the
exercises for a few resources to find out more.
 
 






















   
 
  My own setup
includes a rather slow server, running the Windows NT 4.0 OS, with SP5
and the Apache 1.3.9 Web server for Win32. A more stable combination is
to run Linux instead of NT, with the same version of the Apache server
for Linux. NT is more convenient for me at work right now. I assume that
you have access to a Web server and that you have it set up properly.
Many ISPs provide some form of free Web-page hosting as a fringe benefit
when you buy monthly internet service, although few of these free
services provide Python. Peri is much more common, but call the ISP and
ask for Python. After Python is installed and running, you usually need
to know where it lives on the Web server machine; this is the case with
the Apache Web server, at any rate. Because I installed and maintain my
own Web server, I know where Apache lives and I know where Python lives.
The cgi-bin (the common location for CGI programs) is where I put the
completed Python programs, and on my system, that is in

c:\inetpub\cgi-bin;
Apache lives in


c:\Apache
and
Python in


c:\Python
.
All Python programs placed in the cgi-bin directory must, for Apache,
begin with the ''hash-bang" line,


#!c:\Python\python.exe

or they will not run. In Hour 15, we discussed the


fixhash.py

program for automatically tracking down errors in this first line of
Python programs and fixing those errors, also automatically, and you may
find it useful if you end up maintaining your own Web server.
 
 






















   
 
  If you are not
running your own Web server and you have been able to persuade your ISP
to install Python, you will need to verify that they have installed
Python correctly. All the standard modules must be installed where
Python can find them, and most especially the


cgi.py
module
is required for useful Python CGI programs. If your ISP is running NT,
then all the ISP has to do is run the install program for Python and
inform whatever Web server they are running where Python lives on their
system. It's a little more complicated on Linux, but if the ISP
administrator follows the installation instructions included with the
Python distribution, everything should work correctly. If it does not,
you will have to work with the ISP administrator to attempt to solve the
problem. The Python mailing list may be able to help if you get into
this situation.
 
 






















   
 
  Listing 24.1
shows the traditional "Hello, World!" program modified to run on the
Web.
 
 






















   
 
  Listing 24.1


hellocgi.py
 
 























 
 
 
 1  #!c:\Python\python.exe

 2

 3  def print_content():

 4      print "Content-type: text/html"

 5      print
 
 






















   
 
  continues  
 


 




 


 
















   







Page
447























 
 
 
 6

 7  def print_header():

 8      print ''<html><title>Pythonic Hello World</title><body>"

 9

10  def print_footer():

11      print "</body></html>"

12

13  print_content()

14  print_header()

15  print "Hello, World!"

16  print_footer()

17
 
 






















   
 
 
/usr/bin/python

is where Python lives on my home Linux machine. I mirror my actual Web
site here at home and make sure that as far as is possible, the Python
CGI programs work the same way under both NT and Linux. This setup
allows me to develop Web applications without using the actual server
for testing and development; after programs are debugged on the home
server, I can upload them to the real server, and 99% of the time they
just drop in and run. Figure 24.1 shows what happens when we drop


hellocgi.py

into the cgi-bin directory and access the Web page with Netscape.
 
 






















   
 
 
 
 






















   
 
 
Figure 24.1

Hello, Web world.
 
 






















   
 
  The URL you can
see in the Location field includes not only the Web server address (
http://www.pauahtun.org)
and the name of the script

(hellocgi.py),
but also the CGI bin directory between the two:

cgi-bin.
That's the name of whatever directory is designated as the CGI binary
location on your system; although you can name it anything you want, it
is traditionally called "cgi-bin." As you can see, this test program
doesn't do much. The most important thing to observe in the listing of
the code is the


print_content()

function; no matter how complicated your CGI programs get, you always
need to begin by running this function. It prints the line


Content-type: text/html

to the Web server; this tells the server that the information it is
receiving is to be displayed to the user.
 
 


 




 


 
















   







Page
448






















   
 
  Let's do
something slightly more complicated; we will build a form that users can
fill out from their Web browsers to register for software. When the
Submit button is pushed, the program shown in Listing 24.2 sends email
to you and to the person who submitted the form.
 
 






















   
 
  Listing 24.2


register.py
 
 























 
 
 
 1  #!c:\Python\python.exe

 2  import os

 3  import sys

 4  import cgi

 5  import SMTP

 6

 7  thisscript=os.environ[''SCRIPT_NAME"]





 8  mailhost="mail.callware.com"

 9  mailfrom="ivanlan@callware.com"

10  mailto="ivanlan@callware.com"

11  ccto="ivanlan@callware.com"

12

13  def print_content():

14      print "Content-type: text/html"

15      print

16

17  def print_header():

18      print """<html><title>Pythonic Registration Form</title>

19      <body bgcolor=white text=black>"""

20

21  def print_footer():

22      print "</body></html>"

23

24  def print_form():

25      print """<h1 align=center>Please Register!</h1>

26  <p>Please register software before downloading it. Thank you,

27  %s

28  <hr>

29  <p>

30  <form name=form action=%s method=post>

31    Enter your name:

32    <input type="edit" name="name"><br>

33    Enter your email address:

34    <input type="edit" name="email"><br>

35    Enter the name of the software you are downloading:

36    <input type="edit" name="software"><br>

37    Enter the amount you are willing to pay in dollars for the software:

38    <input type="edit" name="pay"><br>

39    <hr>

40    <input type="submit">

41    <input type="reset">
 
 






















   
 
  continues  
 


 




 


 
















   







Page
449























 
 
 
42  </form>

43  ''"" % (os.environ["SERVER_NAME"],thisscript)

44

45  print_content()

46  print_header()

47

48  form = cgi.FieldStorage()

49  if form.has_key("name") and form.has_key("email"):

50      nm = form["name"].value

51      em = form["email"].value

52      sw = form["software"].value

53      mn = form["pay"].value

54      print """<p align=center><font size=7>Thank you!

55      </font><font size=3><br>

56      <hr>

57      """

58      print """<p align=left>Thank you %s. Your email address, %s,

59      will shortly receive an invoice for %s dollars, for downloading

60      the %s package. If you do not pay up within 5 business days,

61      all your files will be erased.<br>

62      <hr>

63      <p align=right>Have a nice day<img src="../Gif/smiley.gif">

64      """ % (nm,em,mn,sw)

65      msg="""Hello, %s (%s):

66  You recently downloaded %s from %s.

67  Please send %s dollars immediately to:

68

69  Ransom

70  PO Box 6969

71  Washington DC 55512

72

73  If you do not send money, we have a catapult.

74  Thank you for your attention to this matter.

75

76  Anonymous

77  """ %(nm, em, sw, os.environ["SERVER_NAME"],mn)

78

79      s=SMTP.SMTP(mailhost)

80      s.send_message(mailfrom, em,

81          "Software Registration", msg)

82      s.send_message(mailfrom, ccto,

83          "Software Registration", msg)

84      s.close()

85  else:

86      print_form()

87  print_footer()

88
 
 


 




 


 
















   







Page
450






















   
 
  This version is
set up for my official Web site; for your own version, just edit lines
7�11, substituting your Web server's name and cgi-bin directory, where
appropriate, and also changing the email address to your own. When you
enter the correct URL into the Location field of your Web browser, you
should see something like Figure 24.2.
 
 






















   
 
 
 
 






















   
 
 
Figure 24.2

Accessing 
register.py.
 
 






















   
 
  Fill in the form
with your name and correct email address, press the Submit button, and
you should see a Web page similar to Figure 24.3.
 
 






















   
 
 
 
 






















   
 
 
Figure 24.3

Submitting 
register.py.
 
 






















   
 
  Before you can
run this program, you need to obtain Vladimir Ulogov's SMTP module,
which can be found at



http://starship.python.net/crew/gandalf/SMTP.py
.
This module wraps up the standard


smtplib

module to make it extremely easy to use. Lines 79�84 show just how easy
it is to use. As long as you know what your proper mail host is, this
form works just fine on Linux. Windows 95/98, and NT. It will probably
work on Macintoshes, too, but I have no way of testing this.
 
 


 




 


 
















   







Page
451






















   
 
  To analyze what
is happening in the registration program, look at lines 44 onward in
Listing 24.2. The main part of the program starts by printing the
content-type, and then printing the header. This is always the same
(check the title bars in the two previous illustrations), but in line 48
we instantiate an object of class


FieldStorage

from the

cgi
module (imported in line 4). In CGI programs, it is easy to build forms,
but hard to read them after users fill them out. The

cgi
module makes it relatively painless to read them. Line 49 checks to see
whether the

form
object has been filled out; if calls to the


has_key()

method fail, the user has not entered any text into the associated form
element. If line 49 is


false
, we
drop down to line 86 and call the


print_form()

function, which is what displays the blank form shown in Figure 24.2


(print_form()

goes from line 24 to line 44). If, however, line 49 evaluates to

true,
beginning in line 50 we process the information the user has provided,
defining the message to be sent in lines 65�77 and displaying the Web
page shown in Figure 24.3 to let users know their information was
received correctly. After we have defined the message to be emailed,
lines 79�84 create an SMTP object, which connects to your mail server
and sends copies of the message to the appropriate parties (lines 80 and
82). We close the email connection in line 84 after sending our
messages.
 
 






















   
 
  To recap, here
is the basic outline for any Python CGI program you write:
 
 






















   
 
  � Import
modules, define variables and functions (lines 2�44).
 
 






















   
 
  � Define a
function to print a form that has a Submit button (see line 40) and that
uses the
post
method (line 30).
 
 






















   
 
  � Make sure that
you submit the form to the script itself (lines 7 and 43).
 
 






















   
 
  � Create an
instance of the form (line 48).
 
 






















   
 
  � Test to see
whether the form has been filled out (line 49).
 
 






















   
 
  � If no, print
the form (line 86) to the Web browser.
 
 






















   
 
  � If yes, read
the information using


cgi.FieldStorage

and act on it (lines 50�84).
 
 






















   
 
  � Print the end
of the HTML page


(print_footer()

in line 87).
 
 






















   
 
  Armed with this
outline, perseverance, and Python, no CGI programming task should be
beyond you.
 
 






















   
 
  Debugging  
 






















   
 
  On top of my
monitor, I have several large plastic insects. When people ask why I
have them there, I tell them that those bugs are my totems. Usually,
they then ask what totems are, and I tell them that totems are the heads
of animal clans; some Native American tribes have worked out covenants
with, for example, the buffalo totem. What the totem
 
 


 




 


 
















   







Page
452






















   
 
  grants to the
tribes is permission to kill and use individual members of the animal
clan, and thus increase the tribe's chances of survival; what the totem
asks in return is ritual observance, usually in the form of parts of the
animal that are not eaten or used and a ceremony begging forgiveness for
taking a life that is sacred to the totem. If these observances are not
followed, the hunters of the tribe risk offending the totem, who would
retaliate by refusing to send any more individuals of the clan to offer
themselves for the gain of the tribe. (And if you think totemism is not
part of the American way of life, you should start looking around you.
Consider all those ads on TV that feature happy cows, dancing pigs,
singing chickens, and the like�all anthropomorphized and sending the
clear message, ''It's okay to eat us.")
 
 






















   
 
  Debugging
conventional languages is a task that can occupy a significant portion
of the development time for almost any sort of project. In fact, if it
weren't for bugs, the number of software engineers employed today would
be a lot smaller than it is now. Which is why I have my totems out,
visible to everyone; I don't want the totems of the various bug clans to
stop sending me individuals to find and fix. You might think this is
pretty silly, but you never know. It doesn't hurt, and it serves as a
constant reminder that everything is connected to everything else.
Especially in software, where "a weed is a treasure."
 
 






















   
 
  At the same
time, Python is so easy to debug that it's scary; whenever a problem
occurs with a program, the Python interpreter will almost always tell
you exactly which line is at fault. And most of the exception messages
are fairly clear, after you catch on to the basics of the language and
have encountered some of the more common errors a few times. But a
couple of techniques do help that are transferable from more
conventional languages to Python.
 
 






















   
 
  Perhaps the most
basic technique is inserting


print

statements into your code so that you can observe what's happening to
one or more variables during execution of the program. Don't
underestimate the power of this technique, especially in Python; it is
perhaps the quickest way to find out if your design has been accurately
translated into your implementation. Using C or some compiled language,
inserting


printf()

calls into the source is not nearly as efficient, because after
modifying the source, you must recompile the program before you run it.
In Python, you change the program and immediately run it. The overhead
for putting in and taking out


print

statements is usually pretty small in Python.
 
 






















   
 
  The next major
technique is stepping through the code. That is, instead of running the
program as any user normally would, you run it under another program
called a debugger. For command-line Python programs, the debugger is
named

pdb.
The documentation for it can be found at



http://www.python.org/doc/current/lib/module-pdb.html,

and it is quite clear. My own experience with

pdb
has shown me that the best use of it, by
 
 


 




 


 
















   







Page
453






















   
 
  far, is to
proceed step-by-step through a function or method to see if what I think
is happening in the code really is happening. You can use


assert ()
to
do some of the same work without using the debugger, however; it tells
Python, ''Here's what I think is happening; if I'm wrong, throw an
exception." But sometimes there is no substitute whatsoever for using

pdb
to stop after it encounters a certain function, and then to step through
the code in that function line-by-line, inspecting variables to make
sure that they contain the values you think they do and that the
function is doing what you think it should depending on those values.
 
 






















   
 
  But I think
reading the code is still the most important debugging technique.
Whenever something goes wrong, inspect your code. Pretend you are a
computer (and really, when you come down to it, that's what a programmer
is supposed to do best) and step, in your mind, through each line of
your code; fill in variables and try to determine what you are really
telling Python to do here. It may not be what you think is going on, and
reading and rereading your code may sometimes be the only way to
convince yourself that you told Python to do the opposite of what you
meant. If you can't figure out the answer to your problem this way, add
a


print

statement or two; don't go overboard here, or soon you will render your
code unreadable and you will lose track of what you were trying to
accomplish in the first place. If that technique fails, it is time to
fire up the debugger and observe in excruciating detail exactly what is
happening. Detailed coverage of this process is, however, beyond the
scope of this book.
 
 






















   
 
  Right
Practice
 
 
























 
  Zazen practice
is the direct expression of our true nature. Strictly speaking, for a
human being, there is no other practice than this practice; there is no
other way of life than this way of life.

� Shunryu Suzuki, Zen Mind, Beginner's Mind
 
 






















   
 
  Far too much of
modern programming relies on ugly tools and ugly code to produce ugly
results. Plenty of people say, "Who cares if it's ugly, as long as it
works?" Other programmers say, "We don't know what ugly is; we don't
know what beautiful is." They are all right. Gregory Bateson once said
to Margaret Mead, "I don't know what art is." Said Mead, "That's all
right, Gregory. I do."
 
 






















   
 
  No one ever
knows what beautiful is, or what art is, until she does it. The only way
to know whether you're doing art is to do it. This is what is meant by
"right practice." Practice first, analyze later. In other words, you
can't afford to question the validity of what you are doing when you are
doing it; and in Zen terms, when you eat a strawberry, eat the
strawberry. That is, eat the strawberry and do not examine or discuss or
analyze
 
 


 




 


 
















   







Page
454






















   
 
  the eating
utensils, the plates, the whole chain of cause and effect from
strawberry seed to harvesting to table and your plate. The strawberry is
there; it is a fact, and it is to be enjoyed for exactly what it is.
 
 






















   
 
  And of course,
the strawberry, like Python, is connected to everything else, so
sometimes you do have to consider the eating utensils. But most of the
time, you don't have to. This is because Python, unlike so many
programming languages, doesn't force you to pay attention to the wrong
things and the wrong ideas. If you have gone through this book and paid
attention and have practiced regularly, you have learned enough about
Python to let the tools become you. This is the aim of right practice�to
stop thinking about the tools with which you work.
 
 






















   
 
  Right
Attitude
 
 
























 
  When you become
you, Zen becomes Zen. When you are you, you see things as they are, and
you become one with your surroundings.

�Shunryu Suzuki, Zen Mind, Beginner's Mind
 
 






















   
 
  To put it
another way: When Python becomes Python, you become you. Many times
during the early stages of practice, you will become fed up and won't
have any idea why you started trying to learn this language in the first
place. You should be prepared for this. Some schools of Zen hit students
with sticks at crucial points, attempting to awaken them from
complacency and jar their attention back to what they should be
doing�practicing. Other schools of Zen simply tell the student that at
some point they will hate Zen, and that they should be prepared to renew
their efforts at these times.
 
 






















   
 
  At the annual
Maya Meetings in Austin, Texas, the major conference and workshop for
Mayanists in the world, such an attitude has become institutionalized.
The last six days of the conference are given over to hands-on
workshops. I describe the long workshops as ''being locked in a room,
handed untranslated inscriptions, and being told, 'Translate this.'"
First-time participants in these workshops are told, on their first day,
how they will feel during the week. The first couple of days, nothing
will make any sense whatsoever. You will be lost, but you will see that
everyone else is lost and confused, too. By Wednesday afternoon or
Thursday morning, you will be angry. You will want to chuck the whole
thing, climb on a plane, and go home. Ride this out, because by Thursday
afternoon or Friday morning, you will experience an "Aha!" moment; you
will think you understand what you are doing. This is the breakthrough
moment, not because you really understand what's going on (you don't),
not because you suddenly, magically know how to translate glyphs, but
because you attain the right attitude toward what you are doing.
 
 


 




 


 
















   







Page
455






















   
 
  I think that
this is an extremely important process, because for one thing, by
warning beginners up front that their attitudes and feelings throughout
the week are going to follow a pattern similar to what hundreds of other
students have gone through, beginners' attention is focused right away
on practicing and on being prepared for the inevitable course of the
practice. Focus is drawn from the student to the practice; students move
from ''Oh, poor me," to "If I just keep slogging away, they said I'd get
it." And they do. As will you.
 
 






















   
 
  Another
important consequence of this process is that the field of Mayan studies
has grown considerably, and considerably stronger, in the 25 years that
the Maya Meetings have been running, due largely to the simple
assumption by their founder, Linda Schele, that everyone has something
to contribute. Although not every beginner has gone on to become a
famous Mayanist, some few have. Many other students have made important
contributions to the field; some glyphs would never have been translated
without beginners' insights. Linda assumed from the very beginning that
anyone could come to the workshops; everyone was welcome. Attendance was
not limited to an academic elite. The only prerequisite was that you had
to care about epigraphy and Mayan studies. Passion, bordering on
obsession, was not only expected but helpful.
 
 






















   
 
  In programming,
too, passion�bordering on obsession�is expected. To put it another way:
when you have a conversation with someone, it is best to pay attention
to her. Your attitude should not be, "I wish she would shut up so I can
talk about myself," but rather, "If I listen to her instead of talking
about myself, I might learn something interesting. I might be surprised.
My mind might stretch."
 
 






















   
 
  Think of
programming as a conversation or, if you will, a dialectic, between you
and the machine. Your mind might stretch.
 
 






















   
 
  Right
Understanding
 
 
























 
  Zen is not
some kind of excitement, but concentration on our usual everyday
routine. Our understanding of Buddhism is not just an intellectual
understanding. True understanding is actual practice itself.

�Shunryu Suzuki, Zen Mind, Beginner's Mind
 
 






















   
 
  I said a few
lines ago that the breakthrough moment at the Maya Meetings was when
students thought they knew what was going on, when they experienced an
"Aha!" moment. This moment is not true understanding, only the awakening
of right attitude. Right understanding comes much later. It comes when
you understand that the practice and the attitude are one, and that
programming as a process comprises hundreds�if not thousands�of "Aha!"
moments. Not a single moment, but a never-ending stream of them.
 
 


 




 


 
















   







Page
456






















   
 
  Years ago, when
I was trying to write science fiction, I used to wonder where the
''real" authors got their ideas. I struggled with ideas; every time I
thought of something that I could possibly work into a story, I would
write it down on an index card (there were no personal computers then,
and especially, there were no PDAs) and file it carefully away. Nothing
ever came of these because I was so busy searching for and writing down
these ideas that I never wrote the stories. This is a good thing,
because the "ideas" were tired clich�s, and the stories that would have
been written would not have been worth reading.
 
 






















   
 
  "Real" science
fiction authors are constantly asked, "Where do you get your ideas?" In
fact, this is the most asked question. One author used to reply "I steal
them." And this is far more profound than you might think, because it's
what we all do. No idea is new; no idea occurs in a vacuum, like
hydrogen atoms in the steady-state cosmology; no idea is original. All
ideas grow out of the things that influence us, and the things that
influence us are ideas. "New" ideas are simply little twists of concepts
that already exist. By being aware of what is going on around you, by
focusing on your everyday routine, you come to realize, in a
non-intellectual way, that there are needs to be filled, and indeed, a
neverending stream of needs. Some of these needs may be yours, which you
can fulfill by writing a small program in Python to take care of, as we
did earlier with the


fixhash.py

program; some of these needs may be others', in which case you can
analyze the problem and build tools in Python to provide a solution.
Some needs may be a company's needs, in which case Python might provide
the perfect prototyping language, or the perfect glue language, or
sometimes even the perfect production language. But in all cases, when
you write programs, you soon come to understand that every line in your
programs has been written, at one time or another, by someone else,
albeit to serve different needs. You steal every line and change every
line.
 
 






















   
 
  In a sense, all
the lines and words and ideas that make up programs are reused,
recycled, and reinvigorated constantly. It is a process that has been
going on for years and that promises to continue into the foreseeable
future. Carl Sagan said, "We are all made of star-stuff." The atoms that
make us up originated in the incandescent furnaces of novae�exploded
stars that fling these atoms outward to the universe. Eventually,
planetary systems form from the stellar detritus; eventually, life;
eventually, us.
 
 






















   
 
  Where does this
get you? First you practice without understanding; then, you practice
with the attitude, the knowledge, that if you persevere, you will "get
it"; finally, you understand that practice itself is understanding
enough. I mean, here is the point: you learn so much through your
practice, and you practice so much that you gain the kind of attitude
and understanding that sinks below your intellectual mind. Eat the
strawberry without thinking about exactly what you are going to do with
the fork, the plate, the leftover leaves. Ride the bicycle without
wondering how you stay upright. As shown in
 
 


 




 


 
















   







Page
457






















   
 
  Figure 24.4,
write the program without thinking about your tools; simply write the
program. Be the program.
 
 






















   
 
 
 
 






















   
 
 
Figure 24.4

Xuhua Lin drew this calligraphy especially

for this book. It means, loosely translated,

"Python Mind, Beginner's Mind."
 
 






















   
 
  Summary  
 
























 
  The practice of
Zen mind is beginner's mind. The innocence of the first inquiry�what am
I?�is needed throughout Zen practice. The mind of the beginner is empty,
free of the habits of the expert, ready to accept, to doubt, and open to
all the possibilities. It is the kind of mind which can see things as
they are, which step by step and in a flash can realize the original
nature of everything. This practice of Zen mind is found throughout the
book. Directly or sometimes by inference, every section of the book
concerns the question of how to maintain this attitude through your
meditation and in your life. This is an ancient way of teaching, using
the simplest language and the situations of everyday life. This means
the student should teach himself.

�Richard Baker, Introduction to Zen Mind, Beginner's Mind
 
 


 




 


 
















   







Page
458






















   
 
  Workshop  
 






















   
 
  Q&A  
 






















   
 
  Q Did you
ever sell any science fiction?
 
 






















   
 
  A Nope.
But I sure acquired a lot of rejection slips. Some of them I still have;
my favorite is the one that says ''No," scrawled in pencil on a two-inch
square of paper. This was from the Ted White Amazing Stories, by
the way, for those of you who care about such things.
 
 






















   
 
  Q It sounds
like you think that the point of programming is not the programs at all,
but the process and the practice of programming. Is that the case?
 
 






















   
 
  A That is
the nature of the Buddha.
 
 






















   
 
  Exercises  
 






















   
 
  To learn more
about HTML, see HTML: The Definitive Guide, 3rd Edition, by Chuck
Musciano and Bill Kennedy, from O'Reilly. Also useful is Dynamic
HTML: The Definitive Reference,
by Danny Goodman, also from
O'Reilly.
 
 






















   
 
  A massive
compendium of excellent debugging techniques is Code Complete: A
Practical Handbook of Software Construction
by Steve C McConnell.
Microsoft Press; ISBN: 1556154844.
 
 















   
 
  For some more of
my views on eating strawberries (if you're not completely sick of them),
visit



http://www.pauahtun.org/vietnam/abacus.html
.
Actually, I don't care for strawberries, but it sounds better to say
"strawberry" than "tuna steak.


 




No comments: