3.1. If Statements¶
3.1.1. Simple Conditions¶
The statements introduced in this chapter will involve tests or conditions. More syntax for conditions will be introduced later, but for now consider simple arithmetic comparisons that directly translate from math into Python. Try each line separately in the Shell
2 < 5
3 > 7
x = 11
x > 10
2 * x < x
type(True)
You see that conditions are either True
or False
.
These are the only possible Boolean values (named after
19th century mathematician George Boole). In Python the name
Boolean is shortened to the type bool
. It is the type of the
results of true-false conditions or tests.
Note
The Boolean values True
and False
have no quotes around them!
Just as '123'
is a string and 123
without the quotes is not,
'True'
is a string, not of type bool.
3.1.2. Simple if
Statements¶
Run this example program, suitcase.py. Try it at least twice, with inputs: 30 and then 55. As you an see, you get an extra result, depending on the input. The main code is:
weight = float(input("How many pounds does your suitcase weigh? "))
if weight > 50:
print("There is a $25 charge for luggage that heavy.")
print("Thank you for your business.")
The middle two line are an if
statement. It reads pretty much
like English. If it is true that the weight is greater than 50,
then print the statement about an extra charge. If it is not true
that the weight is greater than 50, then don’t do the indented
part: skip printing the extra luggage charge. In any event, when
you have finished with the if
statement (whether it actually does
anything or not), go on to the next statement that is not
indented under the if
. In this case that is the statement
printing “Thank you”.
The general Python syntax for a simple if
statement is
if
condition:
indentedStatementBlock
If the condition is true, then do the indented statements. If the condition is not true, then skip the indented statements.
Another fragment as an example:
if balance < 0:
transfer = -balance
# transfer enough from the backup account:
backupAccount = backupAccount - transfer
balance = balance + transfer
As with other kinds of statements with a heading and an indented block, the block can have more than one statement. The assumption in the example above is that if an account goes negative, it is brought back to 0 by transferring money from a backup account in several steps.
In the examples above the choice is between doing something (if the
condition is True
) or nothing (if the condition is False
).
Often there is a choice of two possibilities, only one of which
will be done, depending on the truth of a condition.
3.1.3. if
-else
Statements¶
Run the example program, clothes.py
. Try it at least twice, with
inputs 50 and then 80. As you can see, you get different results,
depending on the input. The main code of clothes.py
is:
temperature = float(input('What is the temperature? '))
if temperature > 70:
print('Wear shorts.')
else:
print('Wear long pants.')
print('Get some exercise outside.')
The middle four lines are an if-else statement. Again it is
close to English, though you might say “otherwise” instead of
“else” (but else is shorter!). There are two indented blocks:
One, like in the simple if
statement, comes right after the
if
heading and is executed when the condition in the if
heading is true. In the if
-else
form this is followed by an
else:
line, followed by another indented block that is only
executed when the original condition is false. In an if
-else
statement exactly one of two possible indented blocks is executed.
A line is also shown dedented next,
removing indentation, about getting exercise.
Since it is dedented, it is not a part of the if-else statement:
Since its amount of indentation matches the if
heading,
it is always executed in the normal forward flow of statements,
after the if
-else
statement (whichever block is selected).
The general Python if
-else
syntax is
if
condition :indentedStatementBlockForTrueConditionelse:
indentedStatementBlockForFalseCondition
These statement blocks can have any number of statements, and can include about any kind of statement.
3.1.4. More Conditional Expressions¶
All the usual arithmetic comparisons may be made, but many do not use standard mathematical symbolism, mostly for lack of proper keys on a standard keyboard.
Meaning | Math Symbol | Python Symbols |
---|---|---|
Less than | < | < |
Greater than | > | > |
Less than or equal | ≤ | <= |
Greater than or equal | ≥ | >= |
Equals | = | == |
Not equal | ≠ | != |
There should not be space between the two-symbol Python substitutes.
Notice that the obvious choice for equals, a single equal sign, is not used to check for equality. An annoying second equal sign is required. This is because the single equal sign is already used for assignment in Python, so it is not available for tests.
Warning
It is a common error to use only one equal sign when you mean to test for equality, and not make an assignment!
Tests for equality do not make an assignment, and they do not
require a variable on the left. Any expressions can be tested for
equality or inequality (!=
). They do not need to be numbers!
Predict the results and try each line in the Shell:
x = 5
x
x == 5
x == 6
x
x != 6
x = 6
6 == x
6 != x
'hi' == 'h' + 'i'
'HI' != 'hi'
[1, 2] != [2, 1]
An equality check does not make an assignment. Strings are case sensitive. Order matters in a list.
Try in the Shell:
'a' > 5
When the comparison does not make sense, an Exception is caused. [1]
Following up on the discussion of the inexactness of float arithmetic in String Formats for Float Precision, confirm that Python does not consider .1 + .2 to be equal to .3: Write a simple condition into the Shell to test.
Here is another example: Pay with Overtime. Given a person’s work hours for the week and regular hourly wage, calculate the total pay for the week, taking into account overtime. Hours worked over 40 are overtime, paid at 1.5 times the normal rate. This is a natural place for a function enclosing the calculation.
Read the setup for the function:
def calcWeeklyWages(totalHours, hourlyWage):
'''Return the total weekly wages for a worker working totalHours,
with a given regular hourlyWage. Include overtime for hours over 40.
'''
The problem clearly indicates two cases: when no more than 40 hours are worked or when more than 40 hours are worked. In case more than 40 hours are worked, it is convenient to introduce a variable overtimeHours. You are encouraged to think about a solution before going on and examining mine.
You can try running my complete example program, wages.py, also shown below. The format operation at the end of the main function uses the floating point format (String Formats for Float Precision) to show two decimal places for the cents in the answer:
def calcWeeklyWages(totalHours, hourlyWage):
'''Return the total weekly wages for a worker working totalHours,
with a given regular hourlyWage. Include overtime for hours over 40.
'''
if totalHours <= 40:
totalWages = hourlyWage*totalHours
else:
overtime = totalHours - 40
totalWages = hourlyWage*40 + (1.5*hourlyWage)*overtime
return totalWages
def main():
hours = float(input('Enter hours worked: '))
wage = float(input('Enter dollars paid per hour: '))
total = calcWeeklyWages(hours, wage)
print('Wages for {hours} hours at ${wage:.2f} per hour are ${total:.2f}.'
.format(**locals()))
main()
Here the input was intended to be numeric, but it could be decimal
so the conversion from string was via float
, not int
.
Below is an equivalent alternative version of the body of
calcWeeklyWages
, used in wages1.py
. It uses just one
general calculation formula and sets the parameters for the formula
in the if
statement. There are generally a number of ways you might
solve the same problem!
if totalHours <= 40:
regularHours = totalHours
overtime = 0
else:
overtime = totalHours - 40
regularHours = 40
return hourlyWage*regularHours + (1.5*hourlyWage)*overtime
- The
in
boolean operator: There are also Boolean operators that are applied to types others than numbers. A useful Boolean operator is
in
, checking membership in a sequence:>>> vals = ['this', 'is', 'it] >>> 'is' in vals True >>> 'was' in vals False
It can also be used with
not
, asnot in
, to mean the opposite:>>> vals = ['this', 'is', 'it] >>> 'is' not in vals False >>> 'was' not in vals True
In general the two versions are:
itemin
sequenceitemnot in
sequence
Detecting the need for if
statements:
Like with planning programs needing``for`` statements, you want to be able to translate English descriptions of problems that would naturally include if
or if
-else
statements. What are some words or phrases or ideas that suggest the use of
these statements? Think of your own and then compare to a few I gave: [2]
3.1.4.1. Graduate Exercise¶
Write a program, graduate.py
, that prompts students for how
many credits they have. Print whether of not they have enough
credits for graduation. (At Loyola University Chicago 120 credits
are needed for graduation.)
3.1.4.2. Head or Tails Exercise¶
Write a program headstails.py
. It should include a function flip()
,
that simulates a single flip of a coin: It randomly prints either
Heads
or Tails
.
Accomplish this by choosing 0 or 1 arbitrarily with random.randrange(2)
,
and use an if
-else
statement to print Heads
when the result is 0, and Tails
otherwise.
In your main program have a simple repeat loop that calls flip()
10 times to test it, so you generate a random sequence of 10 Heads
and
Tails
.
3.1.4.3. Strange Function Exercise¶
Save the example program jumpFuncStub.py
as jumpFunc.py
,
and complete the definitions of functions jump
and main
as
described in the function documentation strings in the program.
In the jump
function definition use an if
-else
statement (hint [3]).
In the main
function definition
use a for
-each loop, the range function, and the jump function.
The jump
function is introduced for use in
Strange Sequence Exercise, and others after that.
3.1.5. Multiple Tests and if
-elif
Statements¶
Often you want to distinguish between more than two distinct cases,
but conditions only have two possible results, True
or False
,
so the only direct choice is between two options. As anyone who has
played “20 Questions” knows, you can distinguish more cases by
further questions. If there are more than two choices, a single
test may only reduce the possibilities, but further tests can
reduce the possibilities further and further. Since most any kind
of statement can be placed in an indented statement block, one
choice is a further if
statement. For instance consider a
function to convert a numerical grade to a letter grade, ‘A’, ‘B’,
‘C’, ‘D’ or ‘F’, where the cutoffs for ‘A’, ‘B’, ‘C’, and ‘D’ are
90, 80, 70, and 60 respectively. One way to write the function
would be test for one grade at a time, and resolve all the
remaining possibilities inside the next else
clause:
def letterGrade(score):
if score >= 90:
letter = 'A'
else: # grade must be B, C, D or F
if score >= 80:
letter = 'B'
else: # grade must be C, D or F
if score >= 70:
letter = 'C'
else: # grade must D or F
if score >= 60:
letter = 'D'
else:
letter = 'F'
return letter
This repeatedly increasing indentation with an if
statement as
the else
block can be annoying and distracting. A preferred
alternative in this situation, that avoids all this indentation, is
to combine each else
and if
block into an elif
block:
def letterGrade(score):
if score >= 90:
letter = 'A'
elif score >= 80:
letter = 'B'
elif score >= 70:
letter = 'C'
elif score >= 60:
letter = 'D'
else:
letter = 'F'
return letter
The most elaborate syntax for an
if
-elif
-else
statement is indicated in general below:
if
condition1:
indentedStatementBlockForTrueCondition1elif
condition2:
indentedStatementBlockForFirstTrueCondition2elif
condition3:
indentedStatementBlockForFirstTrueCondition3elif
condition4:
indentedStatementBlockForFirstTrueCondition4else:
indentedStatementBlockForEachConditionFalse
The if
, each elif
, and the final else
lines are all
aligned. There can be any number of elif
lines, each followed
by an indented block. (Three happen to be illustrated above.) With
this construction exactly one of the indented blocks is
executed. It is the one corresponding to the first True
condition, or, if all conditions are False
, it is the block
after the final else
line.
Be careful of the strange Python contraction. It is elif
, not
elseif
. A program testing the letterGrade function is in
example program grade1.py
.
See Grade Exercise.
A final alternative for if
statements: if
-elif
-....
with no else
. This would mean changing the syntax for
if
-elif
-else
above so the final else:
and the block after it
would be omitted. It is similar to the basic if
statement
without an else
, in that it is possible for no indented block
to be executed. This happens if none of the conditions in the
tests are true.
With an else
included, exactly one of the indented blocks is
executed. Without an else
, at most one of the indented blocks
is executed.
if weight > 120:
print('Sorry, we can not take a suitcase that heavy.')
elif weight > 50:
print('There is a $25 charge for luggage that heavy.')
This if
-elif
statement only prints a line if there is a
problem with the weight of the suitcase.
3.1.5.1. Sign Exercise¶
Write a program sign.py
to ask the user for a number. Print out
which category the number is in: 'positive'
, 'negative'
, or
'zero'
.
3.1.5.2. Grade Exercise¶
In Idle, load grade1.py
and save it as grade2.py
Modify
grade2.py
so it has an equivalent version of the letterGrade
function that tests in the opposite order, first for F, then D, C, ....
Hint: How many tests do you need to do? [4]
Be sure to run your new version and test with different inputs that test all the different paths through the program. Be careful to test around cut-off points. What does a grade of 79.6 imply? What about exactly 80?
3.1.5.3. Wages Exercise¶
* Modify the wages.py
or the wages1.py
example to create a
program wages2.py
that assumes people are paid double time for
hours over 60. Hence they get paid for at most 20 hours overtime at
1.5 times the normal rate. For example, a person working 65 hours
with a regular wage of $10 per hour would work at $10 per hour for
40 hours, at 1.5 * $10 for 20 hours of overtime, and 2 * $10 for
5 hours of double time, for a total of
10*40 + 1.5*10*20 + 2*10*5 = $800.
You may find wages1.py
easier to adapt than wages.py
.
Be sure to test all paths through the program! Your program is likely to be a modification of a program where some choices worked before, but once you change things, retest for all the cases! Changes can mess up things that worked before.
3.1.6. Nesting Control-Flow Statements¶
The power of a language like Python comes largely from the variety
of ways basic statements can be combined. In particular, for
and if
statements can be nested inside each other’s indented
blocks. For example, suppose you want to print only the positive
numbers from an arbitrary list of numbers in a function with the following heading. Read the pieces for now.
def printAllPositive(numberList):
'''Print only the positive numbers in numberList.'''
For example, suppose numberList
is [3, -5, 2, -1, 0, 7]
.
You want to process a list, so that suggests a for
-each loop,
for num in numberList:
but a for
-each loop runs the same code body for each element
of the list, and we only want
print(num)
for some of them. That seems like
a major obstacle, but think closer at what needs to happen concretely.
As a human, who has eyes of amazing capacity, you are drawn
immediately to the actual correct numbers, 3, 2, and 7, but clearly
a computer doing this systematically will have to check every
number. In fact,
there is a consistent action required: Every number must be
tested to see if it should be printed. This suggests an if
statement, with the condition num > 0
. Try loading into Idle
and running the example program onlyPositive.py
, whose code is
shown below. It ends with a line testing the function:
def printAllPositive(numberList):
'''Print only the positive numbers in numberList.'''
for num in numberList:
if num > 0:
print(num)
printAllPositive([3, -5, 2, -1, 0, 7])
This idea of nesting if
statements enormously expands the
possibilities with loops. Now different things can be done at
different times in loops, as long as there is a consistent test
to allow a choice between the alternatives. Shortly, while
loops
will also be introduced, and you will see if
statements nested inside
of them, too.
The rest of this section deals with graphical examples.
Run example program bounce1.py
. It has a red ball moving and
bouncing obliquely off the edges. If you watch several times, you
should see that it starts from random locations. Also you can
repeat the program from the Shell prompt after you have run the
script. For instance, right after running the program, try in the
Shell
bounceBall(-3, 1)
The parameters give the amount the shape moves in each animation step. You can try other values in the Shell, preferably with magnitudes less than 10.
For the remainder of the description of this example, read the extracted text pieces.
The animations before this were totally scripted, saying exactly
how many moves in which direction, but in this case the direction
of motion changes with every bounce. The program has a graphic
object shape
and the central animation step is
shape.move(dx, dy)
but in this case, dx and dy have to change when the ball gets to a boundary. For instance, imagine the ball getting to the left side as it is moving to the left and up. The bounce obviously alters the horizontal part of the motion, in fact reversing it, but the ball would still continue up. The reversal of the horizontal part of the motion means that the horizontal shift changes direction and therefore its sign:
dx = -dx
but dy
does not need to change. This switch does not happen at
each animation step, but only when the ball reaches the edge of the
window. It happens only some of the time - suggesting an if
statement. Still the condition must be determined. Suppose the
center of the ball has coordinates (x, y). When x reaches some
particular x coordinate, call it xLow, the ball should bounce.
The edge of the window is at coordinate 0, but xLow
should not
be 0, or the ball would be half way off the screen before bouncing!
For the edge of the ball to hit the edge of the screen, the x
coordinate of the center must be the length of the radius away, so
actually xLow
is the radius of the ball.
Animation goes quickly in small steps, so I cheat. I allow the ball
to take one (small, quick) step past where it really should go
(xLow
), and then we reverse it so it comes back to where it
belongs. In particular
if x < xLow:
dx = -dx
There are similar bounding variables xHigh
, yLow
and
yHigh
, all the radius away from the actual edge coordinates,
and similar conditions to test for a bounce off each possible edge.
Note that whichever edge is hit, one coordinate, either dx or dy,
reverses. One way the collection of tests could be written is
if x < xLow:
dx = -dx
if x > xHigh:
dx = -dx
if y < yLow:
dy = -dy
if y > yHigh:
dy = -dy
This approach would cause there to be some extra testing: If it is
true that x < xLow
, then it is impossible for it to be true
that x > xHigh
, so we do not need both tests together. We avoid
unnecessary tests with an elif clause (for both x and y):
if x < xLow:
dx = -dx
elif x > xHigh:
dx = -dx
if y < yLow:
dy = -dy
elif y > yHigh:
dy = -dy
Note that the middle if
is not changed to an elif
,
because it is possible for the ball to reach a corner, and need
both dx
and dy
reversed.
The program also uses several methods to read
part of the state of graphics objects
that we have not used in examples yet. Various graphics objects,
like the circle we are using as the shape, know their center point,
and it can be accessed with the getCenter()
method. (Actually a
clone of the point is returned.) Also each coordinate of a
Point
can be accessed with the getX()
and getY()
methods.
This explains the new features in the central function defined for
bouncing around in a box, bounceInBox
. The animation
arbitrarily goes on in a simple repeat loop for 600 steps. (A later
example will improve this behavior.)
def bounceInBox(shape, dx, dy, xLow, xHigh, yLow, yHigh):
''' Animate a shape moving in jumps (dx, dy), bouncing when
its center reaches the low and high x and y coordinates.
'''
delay = .005
for i in range(600):
shape.move(dx, dy)
center = shape.getCenter()
x = center.getX()
y = center.getY()
if x < xLow:
dx = -dx
elif x > xHigh:
dx = -dx
if y < yLow:
dy = -dy
elif y > yHigh:
dy = -dy
time.sleep(delay)
The program starts the ball from an arbitrary point inside the
allowable rectangular bounds. This is encapsulated in a utility
function included in the program, getRandomPoint
. The
getRandomPoint function uses the randrange
function from the
module random
. Note that in parameters for both the functions
range
and randrange
, the end stated is past the last
value actually desired:
def getRandomPoint(xLow, xHigh, yLow, yHigh):
'''Return a random Point with coordinates in the range specified.'''
x = random.randrange(xLow, xHigh+1)
y = random.randrange(yLow, yHigh+1)
return Point(x, y)
The full program is listed below, repeating bounceInBox
and
getRandomPoint
for completeness. Several parts that may be
useful later, or are easiest to follow as a unit, are separated out
as functions. Make sure you see how it all hangs together or ask
questions!
'''
Show a ball bouncing off the sides of the window.
'''
from graphics import *
import time, random
def bounceInBox(shape, dx, dy, xLow, xHigh, yLow, yHigh):
''' Animate a shape moving in jumps (dx, dy), bouncing when
its center reaches the low and high x and y coordinates.
'''
delay = .005
for i in range(600):
shape.move(dx, dy)
center = shape.getCenter()
x = center.getX()
y = center.getY()
if x < xLow:
dx = -dx
elif x > xHigh:
dx = -dx
if y < yLow:
dy = -dy
elif y > yHigh:
dy = -dy
time.sleep(delay)
def getRandomPoint(xLow, xHigh, yLow, yHigh):
'''Return a random Point with coordinates in the range specified.'''
x = random.randrange(xLow, xHigh+1)
y = random.randrange(yLow, yHigh+1)
return Point(x, y)
def makeDisk(center, radius, win):
'''return a red disk that is drawn in win with given center and radius.'''
disk = Circle(center, radius)
disk.setOutline("red")
disk.setFill("red")
disk.draw(win)
return disk
def bounceBall(dx, dy):
'''Make a ball bounce around the screen, initially moving by (dx, dy)
at each jump.'''
win = GraphWin('Ball Bounce', 290, 290)
win.yUp()
radius = 10
xLow = radius # center is separated from the wall by the radius at a bounce
xHigh = win.getWidth() - radius
yLow = radius
yHigh = win.getHeight() - radius
center = getRandomPoint(xLow, xHigh, yLow, yHigh)
ball = makeDisk(center, radius, win)
bounceInBox(ball, dx, dy, xLow, xHigh, yLow, yHigh)
win.close()
bounceBall(3, 5)
3.1.6.1. Short String Exercise¶
Write a program short.py
with a function printShort
with heading:
def printShort(strings):
'''Given a list of strings,
print the ones with at most three characters.
>>> printShort(['a', 'long', one'])
a
one
'''
In your main program, test the function, calling it several times
with different lists of strings. Hint: Find the length of each string
with the len
function.
The function documentation here models a common
approach: illustrating the behavior of the function with a Python Shell
interaction. This part begins with a line starting with >>>
.
Other exercises and examples will also document behavior in the Shell.
3.1.6.2. Even Print Exercise¶
Write a program even1.py
with a function printEven
with heading:
def printEven(nums):
'''Given a list of integers nums,
print the even ones.
>>> printEven([4, 1, 3, 2, 7])
4
2
'''
In your main program, test the function, calling it several times with different lists of integers. Hint: A number is even if its remainder, when dividing by 2, is 0.
3.1.6.3. Even List Exercise¶
Write a program even2.py
with a function chooseEven
with heading:
def chooseEven(nums):
'''Given a list of integers, nums,
return a list containing only the even ones.
>>> chooseEven([4, 1, 3, 2, 7])
[4, 2]
'''
In your main program, test the function, calling it several times with different lists of integers and printing the results in the main program. (The documentation string illustrates the function call in the Python shell, where the return value is automatically printed. Remember, that in a program, you only print what you explicitly say to print.) Hint: In the function, create a new list, and append the appropriate numbers to it, before returning the result.
3.1.6.4. Unique List Exercise¶
* The madlib2.py
program has its getKeys
function, which first generates
a list of each occurrence of a cue in the story format. This gives the
cues in order, but likely includes repetitions. The original version of getKeys
uses a quick method to remove duplicates, forming a set from the list.
There is a disadvantage in the conversion, though: Sets are not ordered, so
when you iterate through the resulting set, the order of the cues will likely bear no
resemblance to the order they first appeared in the list. That issue
motivates this problem:
Copy madlib2.py
to madlib2a.py
, and add a function with this heading:
def uniqueList(aList):
''' Return a new list that includes the first occurrence of each value
in aList, and omits later repeats. The returned list should include
the first occurrences of values in aList in their original order.
>>> vals = ['cat', 'dog', 'cat', 'bug', 'dog', 'ant', 'dog', 'bug']
>>> uniqueList(vals)
['cat', 'dog', 'bug', 'ant']
'''
Hint: Process aList
in order. Use the in
syntax to
only append elements to a new list that are not
already in the new list.
After perfecting the uniqueList
function, replace the last line of getKeys
,
so it uses uniqueList
to remove duplicates in keyList
.
Check that your madlib2a.py
prompts you for cue values in the order that
the cues first appear in the madlib format string.
3.1.7. Compound Boolean Expressions¶
To be eligible to graduate from Loyola University Chicago, you must have 120 credits and a GPA of at least 2.0. This translates directly into Python as a compound condition:
credits >= 120 and GPA >=2.0
This is true if both credits >= 120
is true and
GPA >= 2.0
is
true. A short example program using this would be:
credits = float(input('How many units of credit do you have? '))
GPA = float(input('What is your GPA? '))
if credits >= 120 and GPA >=2.0:
print('You are eligible to graduate!')
else:
print('You are not eligible to graduate.')
The new Python syntax is for the operator and
:
condition1and
condition2
The compound condition is true if both of the component conditions are true. It is false if at least one of the conditions is false.
See Congress Exercise.
In the last example in the previous section, there was an if
-elif
statement where both tests had the same block to be done if the
condition was true:
if x < xLow:
dx = -dx
elif x > xHigh:
dx = -dx
There is a simpler way to state this in a sentence: If x < xLow or x > xHigh, switch the sign of dx. That translates directly into Python:
if x < xLow or x > xHigh:
dx = -dx
The word or
makes another compound condition:
condition1or
condition2
is true if at least one of the conditions is true. It is false if both conditions are false. This corresponds to one way the word “or” is used in English. Other times in English “or” is used to mean exactly one alternative is true.
Warning
When translating a problem stated in English using “or”, be careful
to determine whether the meaning matches Python’s or
.
It is often convenient to encapsulate complicated tests inside a function. Think how to complete the function starting:
def isInside(rect, point):
'''Return True if the point is inside the Rectangle rect.'''
pt1 = rect.getP1()
pt2 = rect.getP2()
Recall that a Rectangle
is specified in its constructor by two
diagonally oppose Point
s. This example gives the first use in
the tutorials of the Rectangle
methods that recover those two
corner points, getP1
and getP2
. The program calls the
points obtained this way pt1
and pt2
. The x and y coordinates
of pt1
, pt2
, and point
can be recovered with the
methods of the Point
type, getX()
and getY()
.
Suppose that I introduce variables for the x coordinates of pt1
,
point
, and pt2
, calling these x-coordinates end1
,
val
, and end2
, respectively. On first try you might decide
that the needed mathematical relationship to test is
end1 <= val <= end2
Unfortunately, this is not enough: The only requirement for the two
corner points is that they be diagonally opposite, not that the
coordinates of the second point are higher than the corresponding
coordinates of the first point. It could be that end1
is 200;
end2
is 100, and val
is 120. In this latter case val
is between end1
and end2
, but substituting into the
expression above
200 <= 120 <= 100
is False. The 100 and 200 need to be reversed in this case. This
makes a complicated situation. Also this is an issue which must be revisited
for both the x and y coordinates. I introduce an auxiliary function
isBetween
to deal with one coordinate at a time. It starts:
def isBetween(val, end1, end2):
'''Return True if val is between the ends.
The ends do not need to be in increasing order.'''
Clearly this is true if the original expression,
end1 <= val <= end2
, is true. You must also consider the possible
case when the order of the ends is reversed:
end2 <= val <= end1
. How do we combine these two possibilities?
The Boolean connectives to consider are and
and or
.
Which applies? You only need one to be true, so or
is the
proper connective:
A correct but redundant function body would be:
if end1 <= val <= end2 or end2 <= val <= end1:
return True
else:
return False
Check the meaning: if the compound expression is True
, return True
.
If the condition is False
, return False
– in either case return the
same value as the test condition. See that a much simpler and
neater version is to just return the value of the condition
itself!
return end1 <= val <= end2 or end2 <= val <= end1
Note
In general you should not need an if
-else
statement to
choose between true and false values! Operate directly on
the boolean expression.
A side comment on expressions like
end1 <= val <= end2
Other than the two-character operators, this is like standard math syntax, chaining comparisons. In Python any number of comparisons can be chained in this way, closely approximating mathematical notation. Though this is good Python, be aware that if you try other high-level languages like Java and C++, such an expression is gibberish. Another way the expression can be expressed (and which translates directly to other languages) is:
end1 <= val and val <= end2
So much for the auxiliary function isBetween
. Back to the
isInside
function. You can use the isBetween
function to check
the x coordinates,
isBetween(point.getX(), p1.getX(), p2.getX())
and to check the y coordinates,
isBetween(point.getY(), p1.getY(), p2.getY())
Again the question arises: how do you combine the two tests?
In this case we need the point to be both between the sides and between the top and bottom, so the proper connector is and.
Think how to finish the isInside
method. Hint: [5]
Sometimes you want to test the opposite of a condition. As in
English you can use the word not
. For instance, to test if a
Point was not inside Rectangle Rect, you could use the condition
not isInside(rect, point)
In general,
not
condition
is True
when condition is False
, and False
when
condition is True
.
The example program chooseButton1.py
, shown below, is a complete
program using the isInside
function in a simple application,
choosing colors. Pardon the length. Do check it out. It will be the
starting point for a number of improvements that shorten it and
make it more powerful in the next section. First a brief overview:
The program includes the functions isBetween
and isInside
that have
already been discussed. The program creates a number of colored
rectangles to use as buttons and also as picture components. Aside
from specific data values, the code to create each rectangle is the
same, so the action is encapsulated in a function,
makeColoredRect
. All of this is fine, and will be preserved in
later versions.
The present main function is long, though. It has the usual
graphics starting code, draws buttons and picture elements, and
then has a number of code sections prompting the user to choose a
color for a picture element. Each code section has a long
if
-elif
-else
test to see which button was clicked, and sets the
color of the picture element appropriately.
'''Make a choice of colors via mouse clicks in Rectangles --
A demonstration of Boolean operators and Boolean functions.'''
from graphics import *
def isBetween(x, end1, end2):
'''Return True if x is between the ends or equal to either.
The ends do not need to be in increasing order.'''
return end1 <= x <= end2 or end2 <= x <= end1
def isInside(point, rect):
'''Return True if the point is inside the Rectangle rect.'''
pt1 = rect.getP1()
pt2 = rect.getP2()
return isBetween(point.getX(), pt1.getX(), pt2.getX()) and \
isBetween(point.getY(), pt1.getY(), pt2.getY())
def makeColoredRect(corner, width, height, color, win):
''' Return a Rectangle drawn in win with the upper left corner
and color specified.'''
corner2 = corner.clone()
corner2.move(width, -height)
rect = Rectangle(corner, corner2)
rect.setFill(color)
rect.draw(win)
return rect
def main():
win = GraphWin('pick Colors', 400, 400)
win.yUp() # right side up coordinates
redButton = makeColoredRect(Point(310, 350), 80, 30, 'red', win)
yellowButton = makeColoredRect(Point(310, 310), 80, 30, 'yellow', win)
blueButton = makeColoredRect(Point(310, 270), 80, 30, 'blue', win)
house = makeColoredRect(Point(60, 200), 180, 150, 'gray', win)
door = makeColoredRect(Point(90, 150), 40, 100, 'white', win)
roof = Polygon(Point(50, 200), Point(250, 200), Point(150, 300))
roof.setFill('black')
roof.draw(win)
msg = Text(Point(win.getWidth()/2, 375),'Click to choose a house color.')
msg.draw(win)
pt = win.getMouse()
if isInside(pt, redButton):
color = 'red'
elif isInside(pt, yellowButton):
color = 'yellow'
elif isInside(pt, blueButton):
color = 'blue'
else :
color = 'white'
house.setFill(color)
msg.setText('Click to choose a door color.')
pt = win.getMouse()
if isInside(pt, redButton):
color = 'red'
elif isInside(pt, yellowButton):
color = 'yellow'
elif isInside(pt, blueButton):
color = 'blue'
else :
color = 'white'
door.setFill(color)
win.promptClose(msg)
main()
The only further new feature used is in the long return statement
in isInside
.
return isBetween(point.getX(), pt1.getX(), pt2.getX()) and \
isBetween(point.getY(), pt1.getY(), pt2.getY())
Recall that Python is smart enough to realize that a statement
continues to the next line if there is an unmatched pair of
parentheses or brackets. Above is another situation with a long
statement, but there are no unmatched parentheses on a line. For
readability it is best not to make an enormous long line that
would run off your screen or paper. Continuing to the next line is
recommended. You can make the final character on a line be a
backslash ('\\'
) to indicate the statement continues on the next
line. This is not particularly neat, but it is a rather rare
situation. Most statements fit neatly on one line, and the creator
of Python decided it was best to make the syntax simple in the most
common situation. (Many other languages require a special statement
terminator symbol like ‘;’ and pay no attention to newlines).
Extra parentheses here would not hurt, so an alternative would be
return (isBetween(point.getX(), pt1.getX(), pt2.getX()) and
isBetween(point.getY(), pt1.getY(), pt2.getY()) )
The chooseButton1.py program is long partly because of repeated code. The next section gives another version involving lists.
3.1.7.1. Congress Exercise¶
A person is eligible to be a US Senator who is at least 30 years
old and has been a US citizen for at least 9 years. Write an initial version
of a program congress.py
to obtain age and length of
citizenship from the user and print out if a person is eligible to
be a Senator or not.
A person is eligible to be a US Representative
who is at least 25 years old and has been a US citizen for at least
7 years. Elaborate your program congress.py
so it obtains age
and length of citizenship and prints out just the one of the following
three statements that is accurate:
- You are eligible for both the House and Senate.
- You eligible only for the House.
- You are ineligible for Congress.
3.1.8. More String Methods¶
Here are a few more string methods useful in the next exercises,
assuming the methods are applied to a string s
:
s
.startswith(
pre)
returns
True
if string s starts with string pre: Both'-123'.startswith('-')
and'downstairs'.startswith('down')
areTrue
, but'1 - 2 - 3'.startswith('-')
isFalse
.s
.endswith(
suffix)
returns True if string s ends with string suffix: Both
'whoever'.endswith('ever')
and'downstairs'.endswith('airs')
areTrue
, but'1 - 2 - 3'.endswith('-')
isFalse
.s
.replace(
sub,
replacement,
count)
returns a new string with up to the first count occurrences of string sub replaced by replacement. The replacement can be the empty string to delete sub. For example:
s = '-123' t = s.replace('-', '', 1) # t equals '123' t = t.replace('-', '', 1) # t is still equal to '123' u = '.2.3.4.' v = u.replace('.', '', 2) # v equals '23.4.' w = u.replace('.', ' dot ', 5) # w equals '2 dot 3 dot 4 dot '
3.1.8.1. Article Start Exercise¶
In library alphabetizing, if the initial word is an article (“The”, “A”, “An”), then it is ignored when ordering entries. Write a program completing this function, and then testing it:
def startsWithArticle(title):
'''Return True if the first word of title is "The", "A" or "An".'''
Be careful, if the title starts with “There”, it does not start with an article. What should you be testing for?
3.1.8.2. Is Number String Exercise¶
** In the later Safe Number Input Exercise, it will be important to know if
a string can be converted to the desired type of number. Explore that here.
Save example isNumberStringStub.py
as isNumberString.py
and complete
it. It contains headings and documentation strings
for the functions in both parts of this exercise.
A legal whole number string consists entirely of digits.
Luckily strings have an
isdigit
method, which is true when a nonempty string consists
entirely of digits, so
'2397'.isdigit()
returns True
, and '23a'.isdigit()
returns False
, exactly corresponding to the situations when the string
represents a whole number!
In both parts be sure to test carefully. Not only confirm that all
appropriate strings return True
.
Also be sure to test that you return False
for all sorts of bad strings.
Recognizing an integer string is more involved, since it can start with a minus sign (or not). Hence the
isdigit
method is not enough by itself. This part is the most straightforward if you have worked on the sections String Indices and String Slices. An alternate approach works if you use the count method from Object Orientation, and some methods from this section.Complete the function
isIntStr
.Complete the function
isDecimalStr
, which introduces the possibility of a decimal point (though a decimal point is not required). The string methods mentioned in the previous part remain useful.
[1] | This is an improvement that is new in Python 3. |
[2] | “In this case do ___; otherwise”, “if ___, then”, “when ___ is true, then”, “___ depends on whether”, |
[3] | If you divide an even number by 2, what is the remainder? Use this idea
in your if condition. |
[4] | 4 tests to distinguish the 5 cases, as in the previous version |
[5] | Once again, you are calculating and returning a Boolean result. You
do not need an if -else statement. |