Real-life instructions often ask you questions before they tell you what to do. For example,
Computer programs ask questions of their data in a similar way.
For example, the computer program that dispenses money
from a cash machine contains a command like this:
IF the person's account balance is less than the amount of money
that the person wishes to withdraw,
THEN display an error message and return the person's bank card
Here, a question (called a ``test'' or a ``condition,'' prefixed by IF)
triggers a command:
When the answer is ``yes'' (called ''True''), then the
the bank card is returned.
A Mario-Brothers computer game contains complex commands like this one, which
examine the state of the game,
instant by instant, and update the game accordingly:
Move Mario one step, in the direction the human indicated with the joy-stick.
IF Mario has stepped on a gold piece:
THEN
give Mario one more energy point.
ELSE IF
Mario has stepped on a bomb:
THEN
subtract one energy point.
IF Mario's energy points are now zero:
THEN
print "Game Over".
ELSE IF Mario has ...
...
The example shows how one question can lead to other questions.
This style of asking questions
is the secret to how a computer program exhibits human-like intelligence.
To master the style, we must understand the linguistics, the foundations,
and the design applications of
the conditional (''if'') command.
The Python language has a prebuilt ``mini-program''
(a function), called sqrt, that
lives in a file (module) named math.
We use it like this:
$ python
>>> import math
>>> math.sqrt(3)
1.7320508075688772
(The import math command makes the module available, so that
we can use its sqrt mini-program.)
If we try to use sqrt with a negative number, it is a disaster:
>>> math.sqrt(-3)
Traceback (most recent call last):
File "
We wish to prevent such an error.
Here is a small Python script, SQ.py, that
asks the human to type an integer for calculating its square root:
=============================================
# SQ.py computes a square root of a nonnegative int.
# input: a nonnegative int
# output: a nonnegative square root
import math
num = int(raw_input("Type an int: "))
if num >= 0 :
answer = math.sqrt(num)
print answer
raw_input("\npress Enter to finish")
===============================================
The conditional command asks if the input, num,
has a value greater-or-equal to zero. If the answer is yes (True),
then a square root is printed.
Try the script:
$python SQ.py
Type an int: 3
1.73205080757
press Enter to finish
The answer prints --- the program
asked the question (the condition or test),
calculated an answer of True,
and calculated the square root.
Try the script with a negative input:
$python SQ.py
Type an int: -3
press Enter to finish
No answer prints, because the test computed to False --- the
command to compute the square root with sqrt was skipped.
The philosophy behind the conditional command is this:
In the example, we cannot guarantee that the input number is nonnegative, so we ask and only then do we undertake the planned computation.
In the previous chapter, the above example might have been handled
like this, using the assert command:
====================================================
# SQassert uses an assert command to ensure that sqrt works properly.
import math
num = int(raw_input("Type an int: "))
assert num >= 0
answer = math.sqrt(num)
print answer
raw_input("\npress Enter to finish")
==================================================
The above program behaves like this:
$ python SQassert.py
Type an int: -3
Traceback (most recent call last):
File "SQassert.py", line 7, in ?
assert num >= 0
AssertionError
The assert command prevented sqrt from malfunctioning, but
at the cost of quitting the program prematurely and printing
a technical error message. In contrast,
the conditional command seen earlier guides the computer around the
trouble spot to a proper conclusion.
You will see the difference if you save both SQ.py and SQassert.py and start each by double-clicking on their respective icons. Supply a negative number as input to each; what happens? Now, say that you are writing a computer program that controls a cash machine --- if there is bad input to the machine, do you want the machine to avoid the bad computation or do you want it to stop functioning on the spot?
if CONDITION : COMMANDswhere CONDITION is an expression that computes to True or False. A condition typically has the form, EXPRESSSION OP EXPRESSION, where a comparison operation, OP, can be
>, <, >=, <=, ==, !=Read >= as ``greater than or equals to''; read == as ``equals'' (this is not an assignment!); and read != as ``not equals''. An example is:
if (num - 1) >= 0 : print "the num is positive"You must have at least one space between the keyword, if, and the CONDITION, but spaces are not required between the CONDITION and the colon.
The COMMANDs are one or more commands, one command per line.
The commands must be indented, say, by 4 spaces or one tab.
Here is an example:
if num == 0 :
print num,
print "is zero"
print "Have a nice day"
The two print commands are executed when the condition computes
to True, and after the conditional command finishes, the last command,
print "Have a nice day", is always executed --- the
indentation indicates this.
(Notice that num == 0 asks whether the value of num
equals 0.)
Inconsistent indentation within an if-command causes an error --- this example is
wrong
if num == 0 :
print num,
print "is zero"
print "Have a nice day"
because the indentation of the two commands inside the if-command
is inconsistent.
if CONDITION : COMMANDsthe computer does these steps:
Here is an example. In his text, Dawson uses a built-in Python function,
randrange, to generate a random number:
import Random # the built-in function lives in module/file Random
...
die_roll = Random.randrange(1,7)
This generates a random integer in the range of 1 to 6 (!) and
assigns it to die_roll.
Perhaps we build a program that plays a dice game,
and the program asks the user whether she wishes to roll a die
one more time. We write this:
import Random
...
total_score = ... # the player's score so far
question = "Do you want to throw the die again (type y or n)? "
answer = raw_input(question)
if answer == "y" :
die_roll = Random.randrange(1,7)
total_score = total_score + die_roll
print "Your score is", total_score
The if-command compares the user's answer to the string, "y";
if the comparison computes to True,
then a die is ``thrown'' and added to the total score.
If the comparison computes to False, no action is taken.
Finally, the score is printed.
This little example illustrates a standard design pattern: When a program needs more information to continue its computation, it asks the user a question and uses an if-command to analyze the answer.
====================================== import math num = int(raw_input("Type an int: ")) if num >= 0 : # assert: num >= 0 (that is, num is nonnegative and has a square root) answer = math.sqrt(num) # assert: answer is a number print answer raw_input("Press Enter to finish") =======================================The test of the conditional command generates new knowledge --- when the test computes to True, then the knowledge gained ensures that the square-root calculation will proceed correctly. When the test computes to False, the knowledge gained prevents an erroneous attempt to compute square root.
We see this clearly when we draw the program
as a circuit-like flowchart:
At the end of the flowchart, we do not have knowledge whether
num is nonnegative or not --- this is because the two
``paths'' in the ``circuit'' join together.
Here is a second example, where we must ask whether the input
integer is even valued before we print an answer:
================================================
answer = -1
# assert : answer is (an) odd (number)
num = int(raw_input("Type an int:")
if (num % 2) == 0 : # is num an even number (has a remainder of zero) ?
# assert: num is even (if the computer reaches this position,
# it is because the test computed to True)
answer = num + 1
# assert: num is even and answer = num + 1
# implies: answer is odd
# assert: answer is odd
print answer
raw_input("Press Enter to finish")
====================================================
The knowledge generated by the test
ensures that answer is always odd-valued at the
end of the program:
The technical explanation is that, at the conclusion of the
if-command, we assert
assert: (num is even and answer is odd) or (answer is odd)
because one of the two outcomes surely must occur. From
this fact we conclude, regardless of the outcome, that
answer is odd.
The Python method, isdigit, helps us check if a string
contains all digits. For example,
>>> s = "123"
>>> print s.isdigit()
True
>>> t = "ab2"
>>> print t.isdigit()
False
Here is how we use this operation to guard against badly typed,
nonnumeric input:
======================================
import math
text = raw_input("Type an int: "))
if text.isdigit() :
# assert: text holds only digits
num = int(text)
if num >= 0 :
# assert: num >= 0
print math.sqrt(num)
raw_input("\npress Enter to finish")
=======================================
We placed a conditional command inside a conditional command
to ask whether the input is numeric and if it is, then nonnegative.
=========================================================== # SQ2.py computes a square root of a nonnegative int. # input: a nonnegative int # output: a nonnegative square root or an error message import math num = int(raw_input("Type an int: ")) if num >= 0 : # assert: num is nonnegative; it has a square root: print math.sqrt(num) else : # assert: num is < 0 (negative) print "Sorry --- cannot compute square root of a negative number" print # a blank line raw_input("press Enter to finish") =============================================================Try it:
$ python sq2.py Type an int: -3 Sorry --- cannot compute square root of a negative numberThe experiment shows that when the condition is False, the command labelled by else executes.
The syntax format of the if-else-command is
if CONDITION :
TRUE_COMMANDs
else :
FALSE_COMMANDs
where TRUE_COMMANDs and FALSE_COMMANDs
are sequences of one or more commands.
The two groups of commands must be indented by the same amount
of spaces/tabs.
Some people like to visualize an IF-ELSE conditional command as
a ``fork in the road'' in their program; they draw a picture (a flowchart)
of it
like this:
|
v
CONDITION?
/ \
True / \ False
v v
TRUE_COMMANDS FALSE_COMMANDS
\ /
\_ _/
|
v
The two paths fork apart and join together again, displaying the two
possible ``flows of control.''
The picture also inspires this terminology: We call
the TRUE_COMMANDs the
then-arm and
FALSE_COMMANDs the
else-arm. (Many programming languages write a conditional
as
if CONDITION then COMMANDs else COMMANDsS;
the keyword, then,
is omitted from Python's conditional but the terminology is preserved.)
When you design a conditional command, you might find it helpful
to draw the flowchart first and write the Python coding second.
Here is another example, which ensures that we can print a
square root for every integer (because we use the integer's
absolute value):
============================================
# SQalways.py computes a square root for the absolute value of an int.
# asssumed input: an integer
# guaranteed output: the sqaure root for the input's absolute value.
import math
num = int(raw_input("Type an int: "))
if num >= 0 :
absolute_value = num
else :
absolute_value = -num
print math.sqrt(absolute_value)
raw_input("\npress Enter to finish")
===============================================
Perhaps a friendlier version of the above program is one that
asks its user if the square root of a negative number is desired.
Nested conditional commands can do this:
============================================
# SQ3.py computes a square root of an int.
# If the int is negative, the user has the option of computing
# the square root of the number's negation
# input: an int
# output: a nonnegative square root or nothing
import math
num = int(raw_input("Type an int: "))
if num >= 0 :
# assert: num is nonnegative
print math.sqrt(num)
else :
# assert: num is < 0 (negative)
response = raw_input(
"Do you want the square root of the negation? (y or n)? " )
if response == "y" :
print math.sqrt(-num)
else :
print "OK --- bye!"
print # a blank line
raw_input("press Enter to finish")
====================================================
========================================== # SQalways.py computes a square root for the absolute value of an int. # asssumed input: an integer # guaranteed output: the sqaure root for the input's absolute value. num = int(raw_input("Type an int: ")) if num >= 0 : # assert: num >= 0 absolute_value = num # assert: num >= 0 and absolute_value = num # implies: absolute_value >= 0 else : # assert: not(num >= 0) # implies: num < 0 absolute_value = -num # assert: num < 0 and absolute_value = -num # implies absolute_value >= 0 #! assert in both cases: absolute_value >= 0 print math.sqrt(absolute_value) # assert: a number is printed raw_input("\npress Enter to finish") ===========================================
Both the then- and else-arms exploit the answer that the test might calculate to achieve the same fact --- that absolute_value >= 0. Because this fact holds in all cases, we can assert it when the conditional command finishes, at the position labelled by #!. This ensures that a square root is always correctly computed.
Here is a flowchart drawing of the above example:
The pattern of knowledge production for an if-else command
looks like this:
if TEST :
# assert: TEST
THEN_COMMANDS
# assert: GOAL
else :
# assert: not TEST
ELSE_COMMANDs
# assert: GOAL
# assert, in both cases: GOAL
That is,
Here is the same idea, drawn flowchart-style:
|
v
TEST ?
/ \
True / \ False
v v
assert TEST assert not TEST
THEN_COMMANDS ELSE_COMMANDS
assert GOAL assert GOAL
\ /
\_ _/
|
assert GOAL
|
v
===================== num = int( raw_input("Type an int: ")) # Line 1 # 2 if num < 0 : # 3 print "is negative" # 4 else : # 5 print "is nonnegative" # 6 # 7 ============================When the program starts at Line 1, the instruction counter (``i.c.'') holds 1, and there are no variables in the program's namespace. Here is a picture of the computer's configuration:
From now on, we show just the instruction counter and the namespace, since the program stays the same. The instructor counter and the namespace will be typed like this:
i.c.: 1 namespace: (empty)The first command asks the user to enter an integer; say that 42 is typed. This causes 42 to be extracted and saved in the newly creadted variable, num:
i.c.: 3 namespace: num: 42At Line 3, the expression, num < 0 is computed. Of course, the comparison, 42 < 0 computes to False. This makes the instruction counter reset to the first instruction of the conditional's else-arm:
i.c.: 6 namespace: num: 42At instruction 6, the message, is nonnegative, prints, and the instruction counter is set to the command following the conditional:
i.c.: 7 namespace: num: 42
Now, let's repeat the execution with a different test case.
We again start with the instruction counter at 1 and an empty namespace:
i.c.: 1 namespace: (empty)
Say that the user types -3 as the input. At the start of
the conditional, Line 3, we have this configuration:
i.c.: 3 namespace: num: -3
At Line 3, the expression, num < 0 is computed, and
the comparison, -3 < 0 computes to True. This makes
the instruction counter reset to the first instruction of the
conditional's then-arm:
i.c.: 4 namespace: num:-3
At instruction 4, the message, is negative, prints,
and the instruction counter is again set to the command following the
conditional:
i.c.: 7 namespace: num: -3
Here is the example:
===================================================
import math
num = int(raw_input("Type an int: "))
if (num == 0) or (num > 0) : # num is zero or positive ?
print math.sqrt(num)
=====================================================
The or operation is called (logical disjunction): if either (or both!) of its argments computes to True, then the result of the question is True as well.
More precisely stated, the test calculates to True if the first argument, num == 0 , computes to True. If num == 0 computes to False, then the second argument is computed---if num > 0 computes to True then the test computes to True. If both phrases compute to False, then so does the test.
We have used and (logical conjunction)
in program assertions; we can also use it
in tests. Here is a Python coding of the example where we limit
the program to computing square roots in the range of 0 to 10:
===================================================
import math
num = int(raw_input("Type an int: "))
if (num >= 0) and (num <= 10) : # num in range of 0 to 10?
print math.sqrt(num)
else :
print "Sorry --- the number must fall between 0 and 10"
=====================================================
Both expressions of the conjunction must be True for the
test to compute to True:
The first argument, num >= 0, is computed; if it is
False, the else-arm is immediately taken; if it is True, then
num <= 10 is computed, and its result determines which arm
executes.
We can also use not (negation), which ``flips'' the answer
of a test:
if not(num < 0) : #again, is num nonnegative ?
print math.sqrt(num)
Of course, this test asks the same question as does
num >= 0.
Table 5 summarizes the
relational operations and their semantics.
TABLE 5: logical operations===========================================
Operator | Semantics |
E1 and E2 |
conjunction (``and''):
|
E1 or E2 |
disjunction (``or''):
|
not E |
negation(``not''):
|
ENDTABLE================================================================Finally, we can write arbitrarily complex expressions with the relational operations. Here is an example that checks whether a number is either 99 or a nonnegative, even-valued int:
n = int(raw_input("Type an int: ")) if (n == 99) or (not(n < 0) and (n%2 == 0)) : print "it's either 99 or it's a nonnegative, even int"Notice how extra parentheses are used to indicate how the comparisons should be grouped. (The not operator is always applied to the first argument that follows it, here n < 0.) It is safe, however, to omit parentheses when a sequence of the same relational operation is used, e.g.,
if (n == 2) or (n == 3) or (n == 5) or (n > 99) : ...There is no need to group the arguments for the multiple or operations, since the computation will proceed left to right in any case.
# Withdrawal # does subtraction # assumed inputs: arg1 - a nonnegative int # arg2 - an int less-or-equal-to than arg1 # guaranteed output: arg1 - arg2 int arg1 = int(raw_input("Type balance: ")) int arg2 = int(raw_input("Type withdrawal amount: ")) if arg1 > 0 and arg1 >= arg2 : print "New balance is", (arg1 - arg2) else : print "Transaction voided"what results are returned for these argument pairs: 3 and 2 ? 2 and 3 ? 3 and 3 ? -4 and 5 ? 4 and -5 ?
Say that the Withdrawal program is embedded into a cash dispenser. Improve the program so that it behaves ``correctly'' for all five test cases just mentioned.
# SmallPrime # says whether its argument is a prime number less than 10 # assumed input: an int # guaranteed output: a message stating whether the int is a prime # number less than 10 (NOTE: the only primes less than 10 are # 2, 3, 5, and 7.)
Sometimes one wants to use knowledge where there are multiple (more than
two) cases to consider. For example, we wish
to determine whether an input number is either negative, or zero,
or positive.
Here is
Python's way of asking multiple questions within a single conditional:
=================================
# PrintSign
# prints the sign of a number
# asssumed input: an integer
# guaranteed output: a message as to whether or not the
# number is negative, zero, or positive
num = int( raw_input("Type an int: "))
if num < 0 :
print "is negative"
elif num == 0 :
print "is zero"
else : # assert: num > 0 must be true
print "is positive"
raw_input("\n\npress Enter to finish")
======================================
Try it:
$ python PrintSign.py
Type an int: 16
is positive
Again:
$ python PrintSign.py
Type an int: 0
is zero
And again:
$ python PrintSign.py
Type an int: -99
is negative
Read the elif as ``else-if'', which
means that if the previous condition was False, then we ask
another question.
The compound conditional looks like this:
if TEST1 :
COMMANDs1 # do this if TEST1 is True
elif TEST2 : # ask this when TEST1 is False
COMMANDs2 # do this if TEST2 is True
elif TEST3 : # ask this when TEST1 and TEST2 are False
COMMANDs3 # do this if TEST3 is True
... # ... continue this way through all the TESTs....
else :
COMMANDsLAST # do this when _all_ TESTs are False
The semantics of the command is described by the comments inserted
into the syntax description: The conditions are computed one by one,
until a condition computes to True. At that point, the corresponding
set of commands are executed. If all the conditions compute to False,
the commands labelled by else are computed.
Here is a flowchart drawing of the compound conditional:
|
v False False
TEST1? ------> TEST2? ------> TEST3? ... ---+
| | | |
True | True| True| False|
v v v v
COMMANDS1 COMMANDS2 COMMANDS3 COMMANDS_LAST
| | | |
+--------------+--------------+---- ... ----+
|
V
Finally, it is crucial to understand the knowledge that is
generated by asking the series of questions/tests; it looks like this:
if TEST1 :
# assert: TEST1
COMMANDs1
elif TEST2 :
# assert: not TEST1 and TEST2
COMMANDs2
elif TEST3 :
# assert: not TEST1 and not TEST2 and TEST3
COMMANDs3
... # ... continue this way through all the TESTs....
else :
# assert not TEST1 and not TEST2 and not TEST3 ...
COMMANDsLAST
The more questions that are asked, the more knowledge that is gained
for the benefit of the COMMANDs that are finally selected
to compute.
Here is a second example, where a nested conditional asks about the
possible cases that arrive within an input request.
The example is a program
that can convert temperatures from Celsius to Fahrenheit and from
Fahrenheit to Celsius, for example:
$python ConvertTemps.py
Is the temperature Celsius or Fahrenheit (C or F)? C
Please type the temperature: 22
22 degrees Celsius is 71.6 degrees Fahrenheit
There are three possible cases for the first input from the human:
read the base of the temperature read the temperature | V base = "C" ? --> base == "F" ? --> otherwise: | | | V V V convert the convert the print error message Celsius temp Fahrenheit | to Fahrenheit temp to Celsius | | | | +------------+-----------------+ | VHere is the program, which uses a compound conditional to code the flowchart:
FIGURE========================================================== # ConvertTemps converts between Celsius and Fahrenheit temperatures. # assumed input: a base ("C" for Celsius and "F" for Fahrenheit) # the temperature to convert, an int # guaranteed output: the temperature converted to the other base base = raw_input("Is the temperature Celsius or Fahrenheit (C or F)? ") temp = int(raw_input("Please type the temperature: ")) if base == "C" : # assert: base == "C" output_temp = ((9.0 / 5.0) * temp ) + 32 print temp, "degrees Celsius is ", output_temp, "degrees Fahrenheit" elif base == "F" : # assert: base == "F" output_temp = ((5 * temp) - 160) / 9.0 print temp, "degrees Fahrenheit is ", output_temp, "degrees Celsius" else: # assert: base != "C" and base != "F" print "error in the temperature base; no conversion performed" ENDFIGURE======================================================The ``goal'' of each of the three plans of actions is to compute the form of conversion desired by the human. In the third case, when the base is erroneous, no conversion is appropriate.
# WithdrawCash # subtracts its second argument from its first, provided that # the result is nonnegative # assumed inputs: arg1 - the first input, must be a nonnegative float # arg2 - the second argument, a float # guaranteed output: (arg1 - arg2), if arg1 >= arg2;
# Divide # does integer division on its arguments # assumed inputs: arg1 - the dividend, a float # arg2 - the divisor, a float that is not zero # guaranteed output: arg1 / arg2
Flowcharts are a better language for solutions to question-problems, because they are a nice mix of graphics and English. Following is an example.
In practice, two main uses of conditionals are
We desire a program,
TwelveHourClock, which converts a time from
a twenty-four hour clock into the corresponding value on a twelve-hour clock.
Here is a typical behavior:
Type time (24-hour clock): 16:35
The time is 4:35 p.m.
and here is another:
Type time (24-hour clock): 00:05
The time is 12:05 a.m.
The program must be intelligent enough to understand that the hours
0 to 11 are ``a.m.'' hours and that 12 to 23 are ''p.m.''
Also, the ''p.m.'' hours are rewritten as 12, 1, 2, ..., 11,
and the very first ``a.m.'' hour is also 12.
Take a moment to practice these conversions yourself: How do you convert 16:35 to 4:35 p.m.? 00:05 to 12:05 a.m.? 09:00 to 9:00 a.m.? What is the pattern/strategy you use? Can you write it down? It's time to try:
Our first attempt at the algorithm might go like this:
hours == 0 ? hours > 12 ? hours between 1 and 12 ? | | | V V V new_hours = 12 new_hours = hours - 12 new_hours = hours | | | +-----------------+----------------------+ | assert: new_hours is correct hours in 12-hour-clock time | VThe flowchart helps us organize our thinking: The hours value might be any of 0, 1, 2, ..., 12, 13, ..., 23, and our grouping of the 24 values into 3 cases helps us plan the strategies for computation.
You are welcome to write little flowcharts for Steps 3 and 6, which
also have some interest.
Figure 7 shows the program that we code from the flowcharts.
FIGURE 7: program that uses conditionals to convert time==================
# TwelveHourClock
# converts a 24-hour-clock time into a 12-hour-clock time.
# assumed inputs: hour - the hour time, in the range 0..23
# minute - the minutes time, in the range 0..59
# guaranteed output: the 12-hour-clock time
# Step 1:
input = raw_input("Type time (24-hour clock): ")
hours_and_minutes = input.split(":") # split breaks a string into pieces
hour_text = hours_and_minutes[0] # get the first piece (see Chap. 4)
minute_text = hours_and_minutes[1] # get the second piece (see Chap. 4)
if hour_text.isdigit() and minute_text.isdigit() :
# assert: we can convert the inputs into ints
hours = int(hour_text)
minutes = int(minute_text)
# Step 2:
if hours < 0 or hours > 23 or minutes < 0 or minutes > 59 :
print "Error: hours or minutes are out of range"
else :
# assert: hours in 0..23 and minutes in 0..59
# Step 3:
if hours < 12 :
ampm = "a.m."
else :
ampm = "p.m."
# assert in both cases: a.m./p.m. distinction correct
# Step 4:
if hours > 12 :
new_hours = hours - 12
elif hours == 0 :
new_hours = 12
else :
new_hours = hours
# assert in all cases: hours correctly computed
# Step 5: minutes is already correctly computed
# Step 6: print
import string # find string module to use here:
print str(new_hours) + ":" + string.zfill(minutes, 2) + ampm
else :
print "Error: input are not numbers"
raw_input("\npress Enter to finish")
ENDFIGURE============================================================
Within Step 1,
there is a new Python trick, which will be completely understood in
a couple of chapters:
Since the input to the program is a string, which is in fact two numbers
separated by a colon, we must ``split'' the string into its two
pieces. Python has a special method, named split, that we use:
input = raw_input(...)
hours_and_minutes = input.split(":")
The second command splits the string, input
into two pieces, discarding the colon in the middle, and saving
the two string-pieces in variable hours_and_minutes
We extract the two pieces (strings) like this:
hour_text = hours_and_minutes[0]
minute_text = hours_and_minutes[1]
(the details are given in Chapter 5), and we make the two pieces into integers:
hours = int(hour_text)
minutes = int(minute_text)
The split is trick is useful for splitting apart hours-minutes,
dollars-cents, word-word-word, etc.
The remainder of the program makes careful use of the questions we ask of the values of the hours and minutes. When the answer is printed, we use a helper function, string.zfill, which ensures that we print the minutes amounts as a two-digit integer.
# TranslateGrade # converts a numerical score to a letter grade. # assumed input: score - a numerical score that must be in the range 0..100 # guaranteed output: a letter grade based on this scale: # 100..90 = "A"; 89..80 = "B"; 79..70 = "C"; 69..60 = "D"; 59..0 = "F"
Nickels = 1 Pennies = 2
1 quarter 2 dimes 1 penny
The program in Figure 7 is a bit hard to read --- we must remember a long history of tests to know when a command in the program's middle might execute --- so we might ``unnest'' the conditionals in the Figure with a boolean variable, which is a variable that holds as its value either True or False.
Boolean variables are good for remembering a history of knowledge gained from conditionals' tests.
In the time-conversion program, we must ask several questions
about the inputs before we proceed to time conversion. If any
of the answers indicate that the inputs are improper, we must
skip the time conversion.
See Figure 8, which rewrites the
time-conversion program with a boolean variable, OK,
that remembers whether the inputs are acceptable.
FIGURE 8: unnesting conditional statements=============================
# MakeChange2
# calculates change in coins for a dollars, cents amount
# assumed input: dollars - a nonnegative integer
# cents - an integer between 0 and 99
# guaranteed output: the coins for the amount
# Step 1:
input = raw_input("Type time (24-hour clock): ")
hours_and_minutes = input.split(":") # split breaks a string into pieces
hour_text = hours_and_minutes[0] # get the first piece (see Chap. 4)
minute_text = hours_and_minutes[1] # get the second piece (see Chap. 4)
OK = True # asserts whether the inputs are acceptable for time conversion
# Step 2:
if (not hour_text.isdigit()) or not(minute_text.isdigit()) :
print "Error: input are not numbers"
OK = False
if OK :
# assert: we can convert the inputs into ints
hours = int(hour_text)
minutes = int(minute_text)
if hours < 0 or hours > 23 or minutes < 0 or minutes > 59 :
print "Error in inputs"
OK = False
if OK :
# assert: hours in 0..23 and minutes in 0..59, so OK to convert time!
# Step 3:
if hours < 12 :
ampm = "a.m."
else :
ampm = "p.m."
# assert in both cases: a.m./p.m. distinction correct
# Step 4:
if hours > 12 :
new_hours = hours - 12
elif hours == 0 :
new_hours = 12
else :
new_hours = hours
# assert in all cases: hours correctly computed
# Step 5: minutes is already correctly computed
# Step 6: print
import string # find string module to use here:
print str(new_hours) + ":" + string.zfill(minutes, 2) + ampm
raw_input("\npress Enter to finish")
ENDFIGURE=============================================================
The boolean variable is set to False if any test uncovers a bad input. The subsequent commands consult OK to determine whether the computation may proceed. By using OK, we need not write so many nested conditional commands.
================================================ import sys # finds the module with ``system'' functions, like exit # Step 1: input = raw_input("Type time (24-hour clock): ") hours_and_minutes = input.split(":") # split breaks a string into pieces hour_text = hours_and_minutes[0] # get the first piece (see Chap. 4) minute_text = hours_and_minutes[1] # get the second piece (see Chap. 4) # Step 2: if (not hour_text.isdigit()) or not(minute_text.isdigit()) : print "Error: input are not numbers" sys.exit() # STOP DEAD, HERE! # assert: program is still computing, so we can convert the inputs into ints hours = int(hour_text) minutes = int(minute_text) if hours < 0 or hours > 23 or minutes < 0 or minutes > 59 : print "Error in inputs" sys.exit() # assert: program is still computing, # so hours in 0..23 and minutes in 0..59, so OK to convert time! # Step 3: if hours < 12 : ampm = "a.m." else : ampm = "p.m." # assert in both cases: a.m./p.m. distinction correct # Step 4: if hours > 12 : new_hours = hours - 12 elif hours == 0 : new_hours = 12 else : new_hours = hours # assert in all cases: hours correctly computed # Step 5: minutes is already correctly computed # Step 6: print import string # find string module to use here: print str(new_hours) + ":" + string.zfill(minutes, 2) + ampm raw_input("\npress Enter to finish") =====================================================The sys.exit command is actually a separate, small program (``function'') that is prewritten and saved in a package named sys. We must import sys to start the exit program.
Use the exit command with caution --- it stops your program and the Python interpreter, and nothing can happen after it executes. (Demo the above program by saving it and double-clicking on its icon --- what happens when you provide bad input?)
Truly, sys.exit is a ``last resort'' when planning a computation strategy --- use it when you are truly lost and cannot recover from a disastrous situation. (The above use of sys.exit is overkill.)
# ConvertToSeconds # converts an hours, minutes amount into the equivalent time in seconds. # assumed inputs: hours - the hours, a nonnegative integer # minutes - the minutes, an integer in the range 0..59 # guaranteed output: the time in seconds
In the previous chapter, we noted that we should test a program to see if it behaves the way we want. This means we should try the program with the test-case behaviors we started from when we designed the program. (Testing the test-case behaviors is sometimes called ``black box'' testing, because the program is treated like it is resting inside a black box and we do not look inside it to see what happens --- we only watch what the program produces as output.)
But programs that contain conditional statements should undergo a second round of testing where, for every if-command in the program, there is a test case that makes the command's then-arm execute and there is another test case that makes the command's else-arm execute. This ensures that every command inside the program is tested at least once! (Testing the conditions this way is sometimes called ``white box'' or ''clear box'' testing, because we look inside the program to see what happens with the test cases.)
For example,
consider this structure of conditionals, which is checking the
inputs to a banking machine:
if dollars < 0 :
...
else :
...
if cents < 0 :
...
elif cents > 99 :
...
else
...
This asks questions about the input values of dollars and
cents. We must test this program with at least
the following test cases:
As we noted in the previous chapter, it is easy to start a Python program --- double-click on its icon. But if the program contains an error, then the error message will flash onto the display and disappear almost immediately!
It is better to test a new Python program by starting it within a command window:
This starts your program and makes the program show its work in the opened command-prompt window. If there is an error, the error message will appear in the window.
This technique is important, because it is easy to forget to insert colons and uniform indentation into conditional commands. When the Python interpreter notes that you have forgotten to do this, it will issue an error message, and you will certainly want to read it!
The new COMMAND is the CONDIIONAL, which can have these forms of syntax:
if CONDITION : COMMANDs
if CONDITION : COMMANDs1 else : COMMANDs2
if CONDITION1 : COMMANDs1 elif CONDITION2 : COMMANDs2 elif CONDITION3: COMMANDs3 ... else : COMMANDsLAST
A CONDITION is an expression that computes to a boolean (True-False) value. CONDITIONs can be built in these ways:
Boolean variables can be used to simplify the structure of a program
that must ask many questions to choose its course of action.
The style looks like this:
ok = True # remembers if the computation has proceeded correctly so far
... # compute
if BAD_CONDITION :
ok = False
if ok : # if no BAD_CONDITION so far, proceed:
... # compute
if BAD_CONDITION :
ok = False
else :
... # compute
if ok : # if no BAD_CONDITION so far, proceed:
... # compute
If a program must be stopped immediately, use the exit
command:
import sys
sys.exit()
The following special functions were introduced in this chapter:
import Random # the built-in function lives in module/file Random ... die_roll = Random.randrange(1,7)This generates a random integer in the range of 1 to 6 (!) and assigns it to die_roll.
import math math.sqrt(NUMBER)computes the square root of a nonnegative number
import string string.zfill(NUMBER,PLACES)computes a string that represents NUMBER expanded to PLACES-many digits. (For example, print string.zfile(15,5) prints 00015.)
items = STRING.split(SEPARATOR) first = item[0] second = item[1]splits a STRING into two pieces, at the position in the string where the character, SEPARATOR, appears. The pieces are extracted by the second and third commands. The complete explanation is given in Chapter 5.