Jump to content

Archive:Think Python/Classes and functions: Difference between revisions

From IdeaWazaWiki
wikademia>Whiteknight
m Think Python: Automatically uploading HTML source of this book from http://www.greenteapress.com/thinkpython/html/. Will convert to wikitext in a separate step
 
wikademia>Whiteknight
m Partial (mostly) conversion from HTML to Wikitext
Line 1: Line 1:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
{{Think Python/Page}}
            "http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>


<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
== Chapter&#XA0;16&#XA0;&#XA0;Classes and functions ==
<META name="GENERATOR" content="hevea 1.10">
 
<LINK rel="stylesheet" type="text/css" href="book.css">
 
<TITLE>Classes and functions</TITLE>
 
</HEAD>
=== 16.1&#XA0;&#XA0;Time ===
<BODY >
 
<A HREF="book016.html"><IMG SRC="previous_motif.gif" ALT="Previous"></A>
As another example of a user-defined type, we&#X2019;ll define a class called
<A HREF="index.html"><IMG SRC="contents_motif.gif" ALT="Up"></A>
<TT>Time</TT> that records the time of day. The class definition looks
<A HREF="book018.html"><IMG SRC="next_motif.gif" ALT="Next"></A>
like this:
<HR>
 
<H1 CLASS="chapter"><A NAME="htoc191"><FONT COLOR=black><FONT SIZE=3>Chapter&#XA0;16</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Classes and functions</FONT></FONT></H1><P><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="time"></A></P><H2 CLASS="section"><A NAME="toc174"></A><A NAME="htoc192"><FONT COLOR=black><FONT SIZE=3>16.1</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Time</FONT></FONT></H2><P><FONT COLOR=black><FONT SIZE=3>As another example of a user-defined type, we&#X2019;ll define a class called
 
</FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>Time</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> that records the time of day. The class definition looks
 
like this:</FONT></FONT></P><P><A NAME="@default1436"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default1437"></A><FONT COLOR=black><FONT SIZE=3>
<PRE CLASS="verbatim">class Time(object):
</FONT></FONT><A NAME="@default1438"></A><FONT COLOR=black><FONT SIZE=3>
</FONT></FONT><A NAME="@default1439"></A></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>class Time(object):
     """represents the time of day.
     """represents the time of day.
       attributes: hour, minute, second"""
       attributes: hour, minute, second"""
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>We can create a new </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>Time</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> object and assign
</PRE>
attributes for hours, minutes, and seconds:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>time = Time()
We can create a new <TT>Time</TT> object and assign
attributes for hours, minutes, and seconds:
<PRE CLASS="verbatim">time = Time()
time.hour = 11
time.hour = 11
time.minute = 59
time.minute = 59
time.second = 30
time.second = 30
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>The state diagram for the </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>Time</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> object looks like this:</FONT></FONT></P><P><A NAME="@default1440"></A><FONT COLOR=black><FONT SIZE=3>
</PRE>
</FONT></FONT><A NAME="@default1441"></A><FONT COLOR=black><FONT SIZE=3>
The state diagram for the <TT>Time</TT> object looks like this:
</FONT></FONT><A NAME="@default1442"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default1443"></A></P><DIV CLASS="center"><FONT COLOR=black><FONT SIZE=3><IMG SRC="book025.png"></FONT></FONT></DIV><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;1</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
 
</EM></FONT></FONT><A NAME="printtime"></A><FONT COLOR=black><FONT SIZE=3><EM>
 
Write a function called </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>print_time</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> that takes a  
 
Time object and prints it in the form </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>hour:minute:second</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>.
 
Hint: the format sequence </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>'%.2d'</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> prints an integer using
<DIV CLASS="center"><IMG SRC="book025.png"></DIV><DIV CLASS="theorem">'''Exercise&#XA0;1'''&#XA0;&#XA0;''
''''
Write a function called ''<CODE>''print_time''</CODE>'' that takes a  
Time object and prints it in the form ''''<TT>hour:minute:second</TT>''''.
Hint: the format sequence ''<CODE>'''%.2d'''</CODE>'' prints an integer using
at least two digits, including a leading zero if necessary.
at least two digits, including a leading zero if necessary.
</EM></FONT></FONT></DIV><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;2</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
''</DIV><DIV CLASS="theorem">'''Exercise&#XA0;2'''&#XA0;&#XA0;''
</EM></FONT></FONT><A NAME="is_after"></A><P><A NAME="@default1444"></A></P><P><FONT COLOR=black><FONT SIZE=3><EM>Write a boolean function called </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>is_after</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> that
''
takes two Time objects, </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>t1</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> and </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>t2</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>, and
 
returns </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>True</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> if </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>t1</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> follows </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>t2</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> chronologically and
''Write a boolean function called ''<CODE>''is_after''</CODE>'' that
</EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>False</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> otherwise. Challenge: don&#X2019;t use an </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>if</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> statement.
takes two Time objects, ''''<TT>t1</TT>'''' and ''''<TT>t2</TT>'''', and
</EM></FONT></FONT></P></DIV><H2 CLASS="section"><A NAME="toc175"></A><A NAME="htoc193"><FONT COLOR=black><FONT SIZE=3>16.2</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Pure functions</FONT></FONT></H2><P><A NAME="@default1445"></A><FONT COLOR=black><FONT SIZE=3>
returns ''''<TT>True</TT>'''' if ''''<TT>t1</TT>'''' follows ''''<TT>t2</TT>'''' chronologically and
</FONT></FONT><A NAME="@default1446"></A></P><P><FONT COLOR=black><FONT SIZE=3>In the next few sections, we&#X2019;ll write two functions that add time
''''<TT>False</TT>'''' otherwise. Challenge: don&#X2019;t use an ''''<TT>if</TT>'''' statement.
''
</DIV>=== 16.2&#XA0;&#XA0;Pure functions ===
 
 
 
 
In the next few sections, we&#X2019;ll write two functions that add time
values. They demonstrate two kinds of functions: pure functions and
values. They demonstrate two kinds of functions: pure functions and
modifiers. They also demonstrate a development plan I&#X2019;ll call </FONT></FONT><FONT COLOR=black><FONT SIZE=3><B>prototype and patch</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, which is a way of tackling a complex problem
modifiers. They also demonstrate a development plan I&#X2019;ll call '''prototype and patch''', which is a way of tackling a complex problem
by starting with a simple prototype and incrementally dealing with the
by starting with a simple prototype and incrementally dealing with the
complications.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>Here is a simple prototype of </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>add_time</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3>:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def add_time(t1, t2):
complications.
 
Here is a simple prototype of <CODE>add_time</CODE>:
<PRE CLASS="verbatim">def add_time(t1, t2):
     sum = Time()
     sum = Time()
     sum.hour = t1.hour + t2.hour
     sum.hour = t1.hour + t2.hour
Line 53: Line 64:
     sum.second = t1.second + t2.second
     sum.second = t1.second + t2.second
     return sum
     return sum
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>The function creates a new </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>Time</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> object, initializes its
</PRE>
The function creates a new <TT>Time</TT> object, initializes its
attributes, and returns a reference to the new object. This is called
attributes, and returns a reference to the new object. This is called
a </FONT></FONT><FONT COLOR=black><FONT SIZE=3><B>pure function</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3> because it does not modify any of the objects
a '''pure function''' because it does not modify any of the objects
passed to it as arguments and it has no effect,
passed to it as arguments and it has no effect,
like displaying a value or getting user input,  
like displaying a value or getting user input,  
other than returning a value.</FONT></FONT></P><P><A NAME="@default1447"></A><FONT COLOR=black><FONT SIZE=3>
other than returning a value.
</FONT></FONT><A NAME="@default1448"></A></P><P><FONT COLOR=black><FONT SIZE=3>To test this function, I&#X2019;ll create two Time objects: </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>start</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>
 
contains the start time of a movie, like </FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>Monty Python and the
 
Holy Grail</EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, and </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>duration</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> contains the run time of the movie,
 
which is one hour 35 minutes.</FONT></FONT></P><P><A NAME="@default1449"></A></P><P><CODE><FONT COLOR=black><FONT SIZE=3>add_time</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> figures out when the movie will be done.</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>&gt;&gt;&gt; start = Time()
 
To test this function, I&#X2019;ll create two Time objects: <TT>start</TT>
contains the start time of a movie, like ''Monty Python and the
Holy Grail'', and <TT>duration</TT> contains the run time of the movie,
which is one hour 35 minutes.
 
<CODE>add_time</CODE> figures out when the movie will be done.
<PRE CLASS="verbatim">&gt;&gt;&gt; start = Time()
&gt;&gt;&gt; start.hour = 9
&gt;&gt;&gt; start.hour = 9
&gt;&gt;&gt; start.minute = 45
&gt;&gt;&gt; start.minute = 45
Line 75: Line 94:
&gt;&gt;&gt; print_time(done)
&gt;&gt;&gt; print_time(done)
10:80:00
10:80:00
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>The result, </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>10:80:00</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> might not be what you were hoping
</PRE>
The result, <TT>10:80:00</TT> might not be what you were hoping
for. The problem is that this function does not deal with cases where the
for. The problem is that this function does not deal with cases where the
number of seconds or minutes adds up to more than sixty. When that
number of seconds or minutes adds up to more than sixty. When that
happens, we have to &#X201C;carry&#X201D; the extra seconds into the minute column
happens, we have to &#X201C;carry&#X201D; the extra seconds into the minute column
or the extra minutes into the hour column.</FONT></FONT></P><P><A NAME="@default1450"></A></P><P><FONT COLOR=black><FONT SIZE=3>Here&#X2019;s an improved version:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def add_time(t1, t2):
or the extra minutes into the hour column.
 
Here&#X2019;s an improved version:
<PRE CLASS="verbatim">def add_time(t1, t2):
     sum = Time()
     sum = Time()
     sum.hour = t1.hour + t2.hour
     sum.hour = t1.hour + t2.hour
Line 94: Line 117:


     return sum
     return sum
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>Although this function is correct, it is starting to get big.
</PRE>
We will see a shorter alternative later.</FONT></FONT></P><H2 CLASS="section"><A NAME="toc176"></A><A NAME="htoc194"><FONT COLOR=black><FONT SIZE=3>16.3</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Modifiers</FONT></FONT></H2><P><FONT COLOR=black><FONT SIZE=3>
Although this function is correct, it is starting to get big.
</FONT></FONT><A NAME="increment"></A></P><P><A NAME="@default1451"></A><FONT COLOR=black><FONT SIZE=3>
We will see a shorter alternative later.
</FONT></FONT><A NAME="@default1452"></A></P><P><FONT COLOR=black><FONT SIZE=3>Sometimes it is useful for a function to modify the objects it gets as
=== 16.3&#XA0;&#XA0;Modifiers ===
 
 
 
 
 
 
 
Sometimes it is useful for a function to modify the objects it gets as
parameters. In that case, the changes are visible to the caller.
parameters. In that case, the changes are visible to the caller.
Functions that work this way are called </FONT></FONT><FONT COLOR=black><FONT SIZE=3><B>modifiers</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>.</FONT></FONT></P><P><A NAME="@default1453"></A></P><P><FONT COLOR=black><FONT SIZE=3><TT>increment</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, which adds a given number of seconds to a </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>Time</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>
Functions that work this way are called '''modifiers'''.
 
<TT>increment</TT>, which adds a given number of seconds to a <TT>Time</TT>
object, can be written naturally as a
object, can be written naturally as a
modifier. Here is a rough draft:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def increment(time, seconds):
modifier. Here is a rough draft:
<PRE CLASS="verbatim">def increment(time, seconds):
     time.second += seconds
     time.second += seconds


Line 111: Line 145:
         time.minute -= 60
         time.minute -= 60
         time.hour += 1
         time.hour += 1
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>The first line performs the basic operation; the remainder deals
</PRE>
with the special cases we saw before.</FONT></FONT></P><P><A NAME="@default1454"></A></P><P><FONT COLOR=black><FONT SIZE=3>Is this function correct? What happens if the parameter </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>seconds</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>
The first line performs the basic operation; the remainder deals
is much greater than sixty? </FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>In that case, it is not enough to carry
with the special cases we saw before.
once; we have to keep doing it until </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>time.second</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> is less than sixty.
 
One solution is to replace the </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>if</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> statements with </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>while</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>
Is this function correct? What happens if the parameter <TT>seconds</TT>
is much greater than sixty?  
 
In that case, it is not enough to carry
once; we have to keep doing it until <TT>time.second</TT> is less than sixty.
One solution is to replace the <TT>if</TT> statements with <TT>while</TT>
statements. That would make the function correct, but not
statements. That would make the function correct, but not
very efficient.</FONT></FONT></P><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;3</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
very efficient.
Write a correct version of </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>increment</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> that
<DIV CLASS="theorem">'''Exercise&#XA0;3'''&#XA0;&#XA0;''
Write a correct version of ''''<TT>increment</TT>'''' that
doesn&#X2019;t contain any loops.
doesn&#X2019;t contain any loops.
</EM></FONT></FONT></DIV><P><FONT COLOR=black><FONT SIZE=3>Anything that can be done with modifiers can also be done with pure
''</DIV>
Anything that can be done with modifiers can also be done with pure
functions. In fact, some programming languages only allow pure
functions. In fact, some programming languages only allow pure
functions. There is some evidence that programs that use pure
functions. There is some evidence that programs that use pure
functions are faster to develop and less error-prone than programs
functions are faster to develop and less error-prone than programs
that use modifiers. But modifiers are convenient at times,
that use modifiers. But modifiers are convenient at times,
and functional programs tend to be less efficient.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>In general, I recommend that you write pure functions whenever it is
and functional programs tend to be less efficient.
 
In general, I recommend that you write pure functions whenever it is
reasonable and resort to modifiers only if there is a compelling
reasonable and resort to modifiers only if there is a compelling
advantage. This approach might be called a </FONT></FONT><FONT COLOR=black><FONT SIZE=3><B>functional
advantage. This approach might be called a '''functional
programming style</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>.</FONT></FONT></P><P><A NAME="@default1455"></A></P><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;4</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
programming style'''.
Write a &#X201C;pure&#X201D; version of </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>increment</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> that creates and returns
 
<DIV CLASS="theorem">'''Exercise&#XA0;4'''&#XA0;&#XA0;''
Write a &#X201C;pure&#X201D; version of ''''<TT>increment</TT>'''' that creates and returns
a new Time object rather than modifying the parameter.
a new Time object rather than modifying the parameter.
</EM></FONT></FONT></DIV><H2 CLASS="section"><A NAME="toc177"></A><A NAME="htoc195"><FONT COLOR=black><FONT SIZE=3>16.4</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Prototyping versus planning</FONT></FONT></H2><P><FONT COLOR=black><FONT SIZE=3>
''</DIV>=== 16.4&#XA0;&#XA0;Prototyping versus planning ===
</FONT></FONT><A NAME="prototype"></A></P><P><A NAME="@default1456"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default1457"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default1458"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default1459"></A></P><P><FONT COLOR=black><FONT SIZE=3>The development plan I am demonstrating is called &#X201C;prototype and
 
 
 
 
 
 
The development plan I am demonstrating is called &#X201C;prototype and
patch.&#X201D; For each function, I wrote a prototype that performed the
patch.&#X201D; For each function, I wrote a prototype that performed the
basic calculation and then tested it, patching errors along the
basic calculation and then tested it, patching errors along the
way.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>This approach can be effective, especially if you don&#X2019;t yet have a
way.
 
This approach can be effective, especially if you don&#X2019;t yet have a
deep understanding of the problem. But incremental corrections can
deep understanding of the problem. But incremental corrections can
generate code that is unnecessarily complicated&#X2014;since it deals with
generate code that is unnecessarily complicated&#X2014;since it deals with
many special cases&#X2014;and unreliable&#X2014;since it is hard to know if you
many special cases&#X2014;and unreliable&#X2014;since it is hard to know if you
have found all the errors.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>An alternative is </FONT></FONT><FONT COLOR=black><FONT SIZE=3><B>planned development</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, in which high-level
have found all the errors.
 
An alternative is '''planned development''', in which high-level
insight into the problem can make the programming much easier. In
insight into the problem can make the programming much easier. In
this case, the insight is that a Time object is really a three-digit
this case, the insight is that a Time object is really a three-digit
number in base 60 (see </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>wikipedia.org/wiki/Sexagesimal</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>)! The
number in base 60 (see <TT>wikipedia.org/wiki/Sexagesimal</TT>)! The
</FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>second</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> attribute is the &#X201C;ones column,&#X201D; the </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>minute</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>
<TT>second</TT> attribute is the &#X201C;ones column,&#X201D; the <TT>minute</TT>
attribute is the &#X201C;sixties column,&#X201D; and the </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>hour</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> attribute is
attribute is the &#X201C;sixties column,&#X201D; and the <TT>hour</TT> attribute is
the &#X201C;thirty-six hundreds column.&#X201D;</FONT></FONT></P><P><A NAME="@default1460"></A></P><P><FONT COLOR=black><FONT SIZE=3>When we wrote </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>add_time</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> and </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>increment</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, we were effectively
the &#X201C;thirty-six hundreds column.&#X201D;
 
When we wrote <CODE>add_time</CODE> and <TT>increment</TT>, we were effectively
doing addition in base 60, which is why we had to carry from one
doing addition in base 60, which is why we had to carry from one
column to the next.</FONT></FONT></P><P><A NAME="@default1461"></A></P><P><FONT COLOR=black><FONT SIZE=3>This observation suggests another approach to the whole problem&#X2014;we
column to the next.
 
This observation suggests another approach to the whole problem&#X2014;we
can convert Time objects to integers and take advantage of the fact
can convert Time objects to integers and take advantage of the fact
that the computer knows how to do integer arithmetic. </FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>Here is a function that converts Times to integers:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def time_to_int(time):
that the computer knows how to do integer arithmetic.  
 
Here is a function that converts Times to integers:
<PRE CLASS="verbatim">def time_to_int(time):
     minutes = time.hour * 60 + time.minute
     minutes = time.hour * 60 + time.minute
     seconds = minutes * 60 + time.second
     seconds = minutes * 60 + time.second
     return seconds
     return seconds
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>And here is the function that converts integers to Times
</PRE>
(recall that </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>divmod</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> divides the first argument by the second
And here is the function that converts integers to Times
and returns the quotient and remainder as a tuple).</FONT></FONT></P><P><A NAME="@default1462"></A></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def int_to_time(seconds):
(recall that <TT>divmod</TT> divides the first argument by the second
and returns the quotient and remainder as a tuple).
 
<PRE CLASS="verbatim">def int_to_time(seconds):
     time = Time()
     time = Time()
     minutes, time.second = divmod(seconds, 60)
     minutes, time.second = divmod(seconds, 60)
     time.hour, time.minute = divmod(minutes, 60)
     time.hour, time.minute = divmod(minutes, 60)
     return time
     return time
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>You might have to think a bit, and run some tests, to convince
</PRE>
You might have to think a bit, and run some tests, to convince
yourself that these functions are correct. One way to test them is to
yourself that these functions are correct. One way to test them is to
check that </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>time_to_int(int_to_time(x)) == x</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> for many values of
check that <CODE>time_to_int(int_to_time(x)) == x</CODE> for many values of
</FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>x</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>. This is an example of a consistency check.</FONT></FONT></P><P><A NAME="@default1463"></A></P><P><FONT COLOR=black><FONT SIZE=3>Once you are convinced they are correct, you can use them to  
<TT>x</TT>. This is an example of a consistency check.
rewrite </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>add_time</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3>:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def add_time(t1, t2):
 
Once you are convinced they are correct, you can use them to  
rewrite <CODE>add_time</CODE>:
<PRE CLASS="verbatim">def add_time(t1, t2):
     seconds = time_to_int(t1) + time_to_int(t2)
     seconds = time_to_int(t1) + time_to_int(t2)
     return int_to_time(seconds)
     return int_to_time(seconds)
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>This version is shorter than the original, and easier to verify.</FONT></FONT></P><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;5</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
</PRE>
Rewrite </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>increment</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> using </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>time_to_int</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> and </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>int_to_time</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM>.
This version is shorter than the original, and easier to verify.
</EM></FONT></FONT></DIV><P><FONT COLOR=black><FONT SIZE=3>In some ways, converting from base 60 to base 10 and back is harder
<DIV CLASS="theorem">'''Exercise&#XA0;5'''&#XA0;&#XA0;''
Rewrite ''''<TT>increment</TT>'''' using ''<CODE>''time_to_int''</CODE>'' and ''<CODE>''int_to_time''</CODE>''.
''</DIV>
In some ways, converting from base 60 to base 10 and back is harder
than just dealing with times. Base conversion is more abstract; our
than just dealing with times. Base conversion is more abstract; our
intuition for dealing with time values is better.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>But if we have the insight to treat times as base 60 numbers and make
intuition for dealing with time values is better.
the investment of writing the conversion functions (</FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>time_to_int</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3>
 
and </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>int_to_time</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3>), we get a program that is shorter, easier to
But if we have the insight to treat times as base 60 numbers and make
read and debug, and more reliable.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>It is also easier to add features later. For example, imagine
the investment of writing the conversion functions (<CODE>time_to_int</CODE>
and <CODE>int_to_time</CODE>), we get a program that is shorter, easier to
read and debug, and more reliable.
 
It is also easier to add features later. For example, imagine
subtracting two Times to find the duration between them. The
subtracting two Times to find the duration between them. The
na&#XEF;ve approach would be to implement subtraction with borrowing.
na&#XEF;ve approach would be to implement subtraction with borrowing.
Using the conversion functions would be easier and more likely to be
Using the conversion functions would be easier and more likely to be
correct.</FONT></FONT></P><P><A NAME="@default1464"></A><FONT COLOR=black><FONT SIZE=3>
correct.
</FONT></FONT><A NAME="@default1465"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default1466"></A></P><P><FONT COLOR=black><FONT SIZE=3>Ironically, sometimes making a problem harder (or more general) makes it
 
 
 
 
Ironically, sometimes making a problem harder (or more general) makes it
easier (because there are fewer special cases and fewer opportunities
easier (because there are fewer special cases and fewer opportunities
for error).</FONT></FONT></P><H2 CLASS="section"><A NAME="toc178"></A><A NAME="htoc196"><FONT COLOR=black><FONT SIZE=3>16.5</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Debugging</FONT></FONT></H2><P><FONT COLOR=black><FONT SIZE=3>
for error).
</FONT></FONT><A NAME="@default1467"></A></P><P><FONT COLOR=black><FONT SIZE=3>A Time object is well-formed if the values of </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>minutes</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> and </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>seconds</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> are between 0 and 60 (including 0 but not 60) and if  
=== 16.5&#XA0;&#XA0;Debugging ===
</FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>hours</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> is positive. </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>hours</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> and </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>minutes</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> should be
 
integral values, but we might allow </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>seconds</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> to have a
 
fraction part.</FONT></FONT></P><P><A NAME="@default1468"></A></P><P><FONT COLOR=black><FONT SIZE=3>These kind of requirements are called </FONT></FONT><FONT COLOR=black><FONT SIZE=3><B>invariants</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3> because
 
 
A Time object is well-formed if the values of <TT>minutes</TT> and <TT>seconds</TT> are between 0 and 60 (including 0 but not 60) and if  
<TT>hours</TT> is positive. <TT>hours</TT> and <TT>minutes</TT> should be
integral values, but we might allow <TT>seconds</TT> to have a
fraction part.
 
These kind of requirements are called '''invariants''' because
they should always be true. To put it a different way, if they
they should always be true. To put it a different way, if they
are not true, then something has gone wrong.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>Writing code to check your invariants can help you detect errors
are not true, then something has gone wrong.
 
Writing code to check your invariants can help you detect errors
and find their causes. For example, you might have a function
and find their causes. For example, you might have a function
like </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>valid_time</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> that takes a Time object and returns
like <CODE>valid_time</CODE> that takes a Time object and returns
</FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>False</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> if it violates an invariant:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def valid_time(time):
<TT>False</TT> if it violates an invariant:
<PRE CLASS="verbatim">def valid_time(time):
     if time.hours &lt; 0 or time.minutes &lt; 0 or time.seconds &lt; 0:
     if time.hours &lt; 0 or time.minutes &lt; 0 or time.seconds &lt; 0:
         return False
         return False
Line 200: Line 290:
         return False
         return False
     return True
     return True
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>Then at the beginning of each function you could check the
</PRE>
arguments to make sure they are valid:</FONT></FONT></P><P><A NAME="@default1469"></A><FONT COLOR=black><FONT SIZE=3>
Then at the beginning of each function you could check the
</FONT></FONT><A NAME="@default1470"></A></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def add_time(t1, t2):
arguments to make sure they are valid:
 
 
 
<PRE CLASS="verbatim">def add_time(t1, t2):
     if not valid_time(t1) or not valid_time(t2):
     if not valid_time(t1) or not valid_time(t2):
         raise ValueError, 'invalid Time object in add_time'
         raise ValueError, 'invalid Time object in add_time'
     seconds = time_to_int(t1) + time_to_int(t2)
     seconds = time_to_int(t1) + time_to_int(t2)
     return int_to_time(seconds)
     return int_to_time(seconds)
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>Or you could use an </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>assert</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> statement, which checks a given invariant
</PRE>
and raises an exception if it fails:</FONT></FONT></P><P><A NAME="@default1471"></A><FONT COLOR=black><FONT SIZE=3>
Or you could use an <TT>assert</TT> statement, which checks a given invariant
</FONT></FONT><A NAME="@default1472"></A></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def add_time(t1, t2):
and raises an exception if it fails:
 
 
 
<PRE CLASS="verbatim">def add_time(t1, t2):
     assert valid_time(t1) and valid_time(t2)
     assert valid_time(t1) and valid_time(t2)
     seconds = time_to_int(t1) + time_to_int(t2)
     seconds = time_to_int(t1) + time_to_int(t2)
     return int_to_time(seconds)
     return int_to_time(seconds)
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3><TT>assert</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> statements are useful because they distinguish
</PRE>
<TT>assert</TT> statements are useful because they distinguish
code that deals with normal conditions from code
code that deals with normal conditions from code
that checks for errors.</FONT></FONT></P><H2 CLASS="section"><A NAME="toc179"></A><A NAME="htoc197"><FONT COLOR=black><FONT SIZE=3>16.6</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Glossary</FONT></FONT></H2><DL CLASS="description"><DT CLASS="dt-description"><FONT COLOR=black><FONT SIZE=3><B>prototype and patch:</B></FONT></FONT></DT><DD CLASS="dd-description"><FONT COLOR=black><FONT SIZE=3> A development plan that involves
that checks for errors.
=== 16.6&#XA0;&#XA0;Glossary ===
 
<DL CLASS="description"><DT CLASS="dt-description">'''prototype and patch:'''</DT><DD CLASS="dd-description"> A development plan that involves
writing a rough draft of a program, testing, and correcting errors as
writing a rough draft of a program, testing, and correcting errors as
they are found.
they are found.
</FONT></FONT><A NAME="@default1473"></A></DD><DT CLASS="dt-description"><FONT COLOR=black><FONT SIZE=3><B>planned development:</B></FONT></FONT></DT><DD CLASS="dd-description"><FONT COLOR=black><FONT SIZE=3> A development plan that involves
</DD><DT CLASS="dt-description">'''planned development:'''</DT><DD CLASS="dd-description"> A development plan that involves
high-level insight into the problem and more planning than incremental
high-level insight into the problem and more planning than incremental
development or prototype development.
development or prototype development.
</FONT></FONT><A NAME="@default1474"></A></DD><DT CLASS="dt-description"><FONT COLOR=black><FONT SIZE=3><B>pure function:</B></FONT></FONT></DT><DD CLASS="dd-description"><FONT COLOR=black><FONT SIZE=3> A function that does not modify any of the objects it
</DD><DT CLASS="dt-description">'''pure function:'''</DT><DD CLASS="dd-description"> A function that does not modify any of the objects it
receives as arguments. Most pure functions are fruitful.
receives as arguments. Most pure functions are fruitful.
</FONT></FONT><A NAME="@default1475"></A></DD><DT CLASS="dt-description"><FONT COLOR=black><FONT SIZE=3><B>modifier:</B></FONT></FONT></DT><DD CLASS="dd-description"><FONT COLOR=black><FONT SIZE=3> A function that changes one or more of the objects it
</DD><DT CLASS="dt-description">'''modifier:'''</DT><DD CLASS="dd-description"> A function that changes one or more of the objects it
receives as arguments. Most modifiers are fruitless.
receives as arguments. Most modifiers are fruitless.
</FONT></FONT><A NAME="@default1476"></A></DD><DT CLASS="dt-description"><FONT COLOR=black><FONT SIZE=3><B>functional programming style:</B></FONT></FONT></DT><DD CLASS="dd-description"><FONT COLOR=black><FONT SIZE=3> A style of program design in which the
</DD><DT CLASS="dt-description">'''functional programming style:'''</DT><DD CLASS="dd-description"> A style of program design in which the
majority of functions are pure.
majority of functions are pure.
</FONT></FONT><A NAME="@default1477"></A></DD><DT CLASS="dt-description"><FONT COLOR=black><FONT SIZE=3><B>invariant:</B></FONT></FONT></DT><DD CLASS="dd-description"><FONT COLOR=black><FONT SIZE=3> A condition that should always be true during the
</DD><DT CLASS="dt-description">'''invariant:'''</DT><DD CLASS="dd-description"> A condition that should always be true during the
execution of a program.
execution of a program.
</FONT></FONT><A NAME="@default1478"></A></DD></DL><H2 CLASS="section"><A NAME="toc180"></A><A NAME="htoc198"><FONT COLOR=black><FONT SIZE=3>16.7</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Exercises</FONT></FONT></H2><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;6</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
</DD></DL>=== 16.7&#XA0;&#XA0;Exercises ===
Write a function called </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>mul_time</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> that takes a Time object
 
<DIV CLASS="theorem">'''Exercise&#XA0;6'''&#XA0;&#XA0;''
Write a function called ''<CODE>''mul_time''</CODE>'' that takes a Time object
and a number and returns a new Time object that contains
and a number and returns a new Time object that contains
the product of the original Time and the number.</EM></FONT></FONT><P><FONT COLOR=black><FONT SIZE=3><EM>Then use </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>mul_time</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> to write a function that takes a Time
the product of the original Time and the number.''
''Then use ''<CODE>''mul_time''</CODE>'' to write a function that takes a Time
object that represents the finishing time in a race, and a number
object that represents the finishing time in a race, and a number
that represents the distance, and returns a Time object that represents
that represents the distance, and returns a Time object that represents
the average pace (time per mile).</EM></FONT></FONT></P><P><A NAME="@default1479"></A></P></DIV><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;7</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;</FONT></FONT><P><A NAME="@default1480"></A><FONT COLOR=black><FONT SIZE=3><EM>
the average pace (time per mile).''
</EM></FONT></FONT><A NAME="@default1481"></A></P><P><FONT COLOR=black><FONT SIZE=3><EM>Write a class definition for a Date object that has attributes </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>day</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>, </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>month</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> and </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>year</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>. Write a function called
 
</EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>increment_date</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> that takes a Date object, </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>date</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> and an
</DIV><DIV CLASS="theorem">'''Exercise&#XA0;7'''&#XA0;&#XA0;
integer, </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>n</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>, and returns a new Date object that
''
represents the day </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>n</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> days after </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>date</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>. Hint:
''
 
''Write a class definition for a Date object that has attributes ''''<TT>day</TT>'''', ''''<TT>month</TT>'''' and ''''<TT>year</TT>''''. Write a function called
''<CODE>''increment_date''</CODE>'' that takes a Date object, ''''<TT>date</TT>'''' and an
integer, ''''<TT>n</TT>'''', and returns a new Date object that
represents the day ''''<TT>n</TT>'''' days after ''''<TT>date</TT>''''. Hint:
&#X201C;Thirty days hath September...&#X201D; Challenge: does your function
&#X201C;Thirty days hath September...&#X201D; Challenge: does your function
deal with leap years correctly? See </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>wikipedia.org/wiki/Leap_year</TT></EM></FONT></FONT></P></DIV><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;8</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;</FONT></FONT><P><A NAME="@default1482"></A><FONT COLOR=black><FONT SIZE=3><EM>
deal with leap years correctly? See ''''<TT>wikipedia.org/wiki/Leap_year</TT>''
</EM></FONT></FONT><A NAME="@default1483"></A></P><P><FONT COLOR=black><FONT SIZE=3><EM>The </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>datetime</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> module provides </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>date</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> and </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>time</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> objects
</DIV><DIV CLASS="theorem">'''Exercise&#XA0;8'''&#XA0;&#XA0;
''
''
 
''The ''''<TT>datetime</TT>'''' module provides ''''<TT>date</TT>'''' and ''''<TT>time</TT>'''' objects
that are similar to the Date and Time objects in this chapter, but
that are similar to the Date and Time objects in this chapter, but
they provide a rich set of methods and operators. Read the
they provide a rich set of methods and operators. Read the
documentation at </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>docs.python.org/lib/datetime-date.html</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>.</EM></FONT></FONT></P><OL CLASS="enumerate" type=1><LI CLASS="li-enumerate"><FONT COLOR=black><FONT SIZE=3><EM>Use the </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>datetime</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> module to write a program that
documentation at ''''<TT>docs.python.org/lib/datetime-date.html</TT>''''.''
gets the current date and prints the day of the week.</EM></FONT></FONT><P><A NAME="@default1484"></A></P></LI><LI CLASS="li-enumerate"><FONT COLOR=black><FONT SIZE=3><EM>Write a program that takes a birthday as input
 
*''Use the ''''<TT>datetime</TT>'''' module to write a program that
gets the current date and prints the day of the week.''
 
*''Write a program that takes a birthday as input
and prints the user&#X2019;s age and the number of days, hours,
and prints the user&#X2019;s age and the number of days, hours,
minutes and seconds until their next birthday.
minutes and seconds until their next birthday.
</EM></FONT></FONT></LI></OL></DIV><HR>
''
<A HREF="book016.html"><IMG SRC="previous_motif.gif" ALT="Previous"></A>
 
<A HREF="index.html"><IMG SRC="contents_motif.gif" ALT="Up"></A>
</DIV><HR>
<A HREF="book018.html"><IMG SRC="next_motif.gif" ALT="Next"></A>
<IMG SRC="previous_motif.gif" ALT="Previous">
</BODY>
<IMG SRC="contents_motif.gif" ALT="Up">
</HTML>
<IMG SRC="next_motif.gif" ALT="Next">

Revision as of 23:09, 15 September 2008

Chapter 16  Classes and functions

16.1  Time

As another example of a user-defined type, we’ll define a class called Time that records the time of day. The class definition looks like this:



class Time(object):
    """represents the time of day.
       attributes: hour, minute, second"""

We can create a new Time object and assign attributes for hours, minutes, and seconds:

time = Time()
time.hour = 11
time.minute = 59
time.second = 30

The state diagram for the Time object looks like this:



<IMG SRC="book025.png">
Exercise 1  

' Write a function called print_time that takes a Time object and prints it in the form 'hour:minute:second'. Hint: the format sequence %.2d prints an integer using at least two digits, including a leading zero if necessary.

Exercise 2  

Write a boolean function called is_after that takes two Time objects, 't1' and 't2', and returns 'True' if 't1' follows 't2' chronologically and 'False' otherwise. Challenge: don’t use an 'if' statement.

=== 16.2  Pure functions ===



In the next few sections, we’ll write two functions that add time values. They demonstrate two kinds of functions: pure functions and modifiers. They also demonstrate a development plan I’ll call prototype and patch, which is a way of tackling a complex problem by starting with a simple prototype and incrementally dealing with the complications.

Here is a simple prototype of add_time:

def add_time(t1, t2):
    sum = Time()
    sum.hour = t1.hour + t2.hour
    sum.minute = t1.minute + t2.minute
    sum.second = t1.second + t2.second
    return sum

The function creates a new Time object, initializes its attributes, and returns a reference to the new object. This is called a pure function because it does not modify any of the objects passed to it as arguments and it has no effect, like displaying a value or getting user input, other than returning a value.



To test this function, I’ll create two Time objects: start contains the start time of a movie, like Monty Python and the Holy Grail, and duration contains the run time of the movie, which is one hour 35 minutes.

add_time figures out when the movie will be done.

>>> start = Time()
>>> start.hour = 9
>>> start.minute = 45
>>> start.second =  0

>>> duration = Time()
>>> duration.hour = 1
>>> duration.minute = 35
>>> duration.second = 0

>>> done = add_time(start, duration)
>>> print_time(done)
10:80:00

The result, 10:80:00 might not be what you were hoping for. The problem is that this function does not deal with cases where the number of seconds or minutes adds up to more than sixty. When that happens, we have to “carry” the extra seconds into the minute column or the extra minutes into the hour column.

Here’s an improved version:

def add_time(t1, t2):
    sum = Time()
    sum.hour = t1.hour + t2.hour
    sum.minute = t1.minute + t2.minute
    sum.second = t1.second + t2.second

    if sum.second >= 60:
        sum.second -= 60
        sum.minute += 1

    if sum.minute >= 60:
        sum.minute -= 60
        sum.hour += 1

    return sum

Although this function is correct, it is starting to get big. We will see a shorter alternative later.

16.3  Modifiers

Sometimes it is useful for a function to modify the objects it gets as parameters. In that case, the changes are visible to the caller. Functions that work this way are called modifiers.

increment, which adds a given number of seconds to a Time object, can be written naturally as a modifier. Here is a rough draft:

def increment(time, seconds):
    time.second += seconds

    if time.second >= 60:
        time.second -= 60
        time.minute += 1

    if time.minute >= 60:
        time.minute -= 60
        time.hour += 1

The first line performs the basic operation; the remainder deals with the special cases we saw before.

Is this function correct? What happens if the parameter seconds is much greater than sixty?

In that case, it is not enough to carry once; we have to keep doing it until time.second is less than sixty. One solution is to replace the if statements with while statements. That would make the function correct, but not very efficient.

Exercise 3  

Write a correct version of 'increment' that doesn’t contain any loops.

Anything that can be done with modifiers can also be done with pure functions. In fact, some programming languages only allow pure functions. There is some evidence that programs that use pure functions are faster to develop and less error-prone than programs that use modifiers. But modifiers are convenient at times, and functional programs tend to be less efficient.

In general, I recommend that you write pure functions whenever it is reasonable and resort to modifiers only if there is a compelling advantage. This approach might be called a functional programming style.

Exercise 4  

Write a “pure” version of 'increment' that creates and returns a new Time object rather than modifying the parameter.

=== 16.4  Prototyping versus planning ===





The development plan I am demonstrating is called “prototype and patch.” For each function, I wrote a prototype that performed the basic calculation and then tested it, patching errors along the way.

This approach can be effective, especially if you don’t yet have a deep understanding of the problem. But incremental corrections can generate code that is unnecessarily complicated—since it deals with many special cases—and unreliable—since it is hard to know if you have found all the errors.

An alternative is planned development, in which high-level insight into the problem can make the programming much easier. In this case, the insight is that a Time object is really a three-digit number in base 60 (see wikipedia.org/wiki/Sexagesimal)! The second attribute is the “ones column,” the minute attribute is the “sixties column,” and the hour attribute is the “thirty-six hundreds column.”

When we wrote add_time and increment, we were effectively doing addition in base 60, which is why we had to carry from one column to the next.

This observation suggests another approach to the whole problem—we can convert Time objects to integers and take advantage of the fact that the computer knows how to do integer arithmetic.

Here is a function that converts Times to integers:

def time_to_int(time):
    minutes = time.hour * 60 + time.minute
    seconds = minutes * 60 + time.second
    return seconds

And here is the function that converts integers to Times (recall that divmod divides the first argument by the second and returns the quotient and remainder as a tuple).

def int_to_time(seconds):
    time = Time()
    minutes, time.second = divmod(seconds, 60)
    time.hour, time.minute = divmod(minutes, 60)
    return time

You might have to think a bit, and run some tests, to convince yourself that these functions are correct. One way to test them is to check that time_to_int(int_to_time(x)) == x for many values of x. This is an example of a consistency check.

Once you are convinced they are correct, you can use them to rewrite add_time:

def add_time(t1, t2):
    seconds = time_to_int(t1) + time_to_int(t2)
    return int_to_time(seconds)

This version is shorter than the original, and easier to verify.

Exercise 5  

Rewrite 'increment' using time_to_int and int_to_time.

In some ways, converting from base 60 to base 10 and back is harder than just dealing with times. Base conversion is more abstract; our intuition for dealing with time values is better.

But if we have the insight to treat times as base 60 numbers and make the investment of writing the conversion functions (time_to_int and int_to_time), we get a program that is shorter, easier to read and debug, and more reliable.

It is also easier to add features later. For example, imagine subtracting two Times to find the duration between them. The naïve approach would be to implement subtraction with borrowing. Using the conversion functions would be easier and more likely to be correct.



Ironically, sometimes making a problem harder (or more general) makes it easier (because there are fewer special cases and fewer opportunities for error).

16.5  Debugging

A Time object is well-formed if the values of minutes and seconds are between 0 and 60 (including 0 but not 60) and if hours is positive. hours and minutes should be integral values, but we might allow seconds to have a fraction part.

These kind of requirements are called invariants because they should always be true. To put it a different way, if they are not true, then something has gone wrong.

Writing code to check your invariants can help you detect errors and find their causes. For example, you might have a function like valid_time that takes a Time object and returns False if it violates an invariant:

def valid_time(time):
    if time.hours < 0 or time.minutes < 0 or time.seconds < 0:
        return False
    if time.minutes >= 60 or time.seconds >= 60:
        return False
    return True

Then at the beginning of each function you could check the arguments to make sure they are valid:


def add_time(t1, t2):
    if not valid_time(t1) or not valid_time(t2):
        raise ValueError, 'invalid Time object in add_time'
    seconds = time_to_int(t1) + time_to_int(t2)
    return int_to_time(seconds)

Or you could use an assert statement, which checks a given invariant and raises an exception if it fails:


def add_time(t1, t2):
    assert valid_time(t1) and valid_time(t2)
    seconds = time_to_int(t1) + time_to_int(t2)
    return int_to_time(seconds)

assert statements are useful because they distinguish code that deals with normal conditions from code that checks for errors.

16.6  Glossary

prototype and patch:
A development plan that involves writing a rough draft of a program, testing, and correcting errors as they are found.
planned development:
A development plan that involves high-level insight into the problem and more planning than incremental development or prototype development.
pure function:
A function that does not modify any of the objects it receives as arguments. Most pure functions are fruitful.
modifier:
A function that changes one or more of the objects it receives as arguments. Most modifiers are fruitless.
functional programming style:
A style of program design in which the majority of functions are pure.
invariant:
A condition that should always be true during the execution of a program.

=== 16.7  Exercises ===

Exercise 6  

Write a function called mul_time that takes a Time object and a number and returns a new Time object that contains the product of the original Time and the number. Then use mul_time to write a function that takes a Time object that represents the finishing time in a race, and a number that represents the distance, and returns a Time object that represents the average pace (time per mile).

Exercise 7  

Write a class definition for a Date object that has attributes 'day', 'month' and 'year'. Write a function called increment_date that takes a Date object, 'date' and an integer, 'n', and returns a new Date object that represents the day 'n' days after 'date'. Hint: “Thirty days hath September...” Challenge: does your function deal with leap years correctly? See ''wikipedia.org/wiki/Leap_year

Exercise 8  

The 'datetime' module provides 'date' and 'time' objects that are similar to the Date and Time objects in this chapter, but they provide a rich set of methods and operators. Read the documentation at 'docs.python.org/lib/datetime-date.html'.

  • Use the 'datetime' module to write a program that

gets the current date and prints the day of the week.

  • Write a program that takes a birthday as input

and prints the user’s age and the number of days, hours, minutes and seconds until their next birthday.


<IMG SRC="previous_motif.gif" ALT="Previous"> <IMG SRC="contents_motif.gif" ALT="Up"> <IMG SRC="next_motif.gif" ALT="Next">