ይህ ትምህርት ገና ወደ አማርኛ አልተተረጎመም፤ ስለዚህ በእንግሊዝኛ ቀርቧል። የእንግሊዝኛውን ገጽ ክፈቱ
Project: Refactor with functions
In lesson 2.6 you built a grade calculator as one long block. Now you'll rebuild it from small functions, each with one job, and see what that buys you: code that checks itself, and pieces you can reuse in the guessing game from lesson 3.5 without copying.
Here's the finished grade calculator, with a mistyped score first:
Score (0-100): 101
Please enter a number from 0 to 100.
Score (0-100): 85
Grade: A (4.0 points)The whole program is four functions, built one milestone at a time. Keep these names; later milestones reuse them:
| Function | Its one job |
|---|---|
grade_for(score) | Return the letter for a score. Never prints. |
grade_points(letter) | Return the points for a letter. Never prints. |
ask_number(prompt, low, high) | Ask until the answer is a whole number in range; return it. |
main() | Run the program: ask, grade, print. |
Milestone 1: grade_for, and a test
Start with the grading rule from lesson 2.6, as a function that returns the letter. Because it returns, a few lines can check it at every boundary: each pair below is a score and the letter it should get. The check prints nothing for a right answer and a clear line for a wrong one.
መልመጃ
grade_for passes every check
Write grade_for(score) using lesson 2.6's table: 85 and up is A, 70 to 84 is B, 50
to 69 is C, below 50 is F. The starter version returns F for everything, so the
checks report every score that isn't an F. Make the checks pass.
Your code
def grade_for(score):
# Return "A", "B", "C", or "F"
return "F"
checks = [(0, "F"), (49, "F"), (50, "C"), (69, "C"), (70, "B"), (84, "B"), (85, "A"), (100, "A")]
for score, expected in checks:
if grade_for(score) != expected:
print(f"Wrong for {score}: got {grade_for(score)}, expected {expected}")
print("All checks done")In the editor, press Escape then Tab to move on.
መፍትሄውን አሳይHide the solution
This is one way to solve it, not the only one. If yours prints the same thing, it works.
def grade_for(score):
if score >= 85:
return "A"
elif score >= 70:
return "B"
elif score >= 50:
return "C"
else:
return "F"
checks = [(0, "F"), (49, "F"), (50, "C"), (69, "C"), (70, "B"), (84, "B"), (85, "A"), (100, "A")]
for score, expected in checks:
if grade_for(score) != expected:
print(f"Wrong for {score}: got {grade_for(score)}, expected {expected}")
print("All checks done")Now break it on purpose: change >= 85 to > 85 and run it. The checks catch it
straight away:
def grade_for(score):
if score > 85:
return "A"
elif score >= 70:
return "B"
elif score >= 50:
return "C"
else:
return "F"
checks = [(0, "F"), (49, "F"), (50, "C"), (69, "C"), (70, "B"), (84, "B"), (85, "A"), (100, "A")]
for score, expected in checks:
if grade_for(score) != expected:
print(f"Wrong for {score}: got {grade_for(score)}, expected {expected}")
print("All checks done")In the editor, press Escape then Tab to move on.
This is why grade_for returns instead of printing. Predict what a printing version
gives the check:
ውጤቱን ገምቱ
Decide before you look. Guessing wrong is how this sticks.
def grade_for(score):
if score >= 85:
print("A")
else:
print("F")
print(grade_for(90) == "A")Python prints
A False
The call prints A for a person to read, then returns None. None == "A" is False, so a check would call a correct grade wrong.
A printed answer has to be checked by a person reading it. A returned value can be
compared with != by the program itself: in lesson 2.6 you typed ten scores in by hand;
now eight checks run every time you change the code. Module 9 builds on exactly this.
Milestone 2: ask_number
The calculator needs a score from 0 to 100; the guessing game needs a guess from 1 to
100. One function can do both if the range is passed in. text.isdigit() (lesson 3.5)
checks the text is all digits before int() converts it, so ninety can't crash it.
መልመጃ
Ask until the answer is valid
Write ask_number(prompt, low, high): ask with input(prompt) until the answer is
all digits and from low to high, then return it as an int. For a bad answer,
print Please enter a number from 0 to 100. (with the real low and high). The
Input box has a word, a score that's too high, then 85.
Your code
def ask_number(prompt, low, high):
# Ask until the text is all digits and in range, then return it as an int
return int(input(prompt))
score = ask_number("Score (0-100): ", 0, 100)
print(score)In the editor, press Escape then Tab to move on.
One answer per line, given to input() in order.
መፍትሄውን አሳይHide the solution
This is one way to solve it, not the only one. If yours prints the same thing, it works.
def ask_number(prompt, low, high):
while True:
text = input(prompt)
if text.isdigit() and low <= int(text) <= high:
return int(text)
print(f"Please enter a number from {low} to {high}.")
score = ask_number("Score (0-100): ", 0, 100)
print(score)ጥያቄ
ask_number loops with while True and has no break. What ends the loop?
isdigit() comes first in the and, so int(text) only runs on digits (lesson 2.3).
Lesson 7.2 rewrites ask_number the better way, trying int() and handling the error,
so it accepts numbers like -3 and asks again, instead of stopping, if someone types ፩.
Milestone 3: main()
grade_points uses a dict (lesson 4.4). main() is the only function that asks or
prints; the others just calculate. You'll see if __name__ == "__main__": at the
bottom of most Python programs; for now, read it as "if this file is the one being
run". Lesson 8.5 shows why it matters.
መልመጃ
The whole calculator
Add grade_points(letter) and main() to your grade_for and ask_number, and
print the result as Grade: A (4.0 points). The Input box has 101, then 85.
Your code
# Paste grade_for and ask_number here
def grade_points(letter):
# Return 4.0, 3.0, 2.0, or 0.0
pass
def main():
# Ask for the score, grade it, and print the result
pass
if __name__ == "__main__":
main()In the editor, press Escape then Tab to move on.
One answer per line, given to input() in order.
መፍትሄውን አሳይHide the solution
This is one way to solve it, not the only one. If yours prints the same thing, it works.
def grade_for(score):
if score >= 85:
return "A"
elif score >= 70:
return "B"
elif score >= 50:
return "C"
else:
return "F"
def grade_points(letter):
points = {"A": 4.0, "B": 3.0, "C": 2.0, "F": 0.0}
return points[letter]
def ask_number(prompt, low, high):
while True:
text = input(prompt)
if text.isdigit() and low <= int(text) <= high:
return int(text)
print(f"Please enter a number from {low} to {high}.")
def main():
score = ask_number("Score (0-100): ", 0, 100)
grade = grade_for(score)
print(f"Grade: {grade} ({grade_points(grade)} points)")
if __name__ == "__main__":
main()When you move code into functions, keep the call at the bottom. Python runs the file
from the top, and a def has to run before its function can be called:
Read main() aloud: ask for a number, get its grade, print it. Each line hides the
details in a function with a clear name, and each function can be checked on its own.
Milestone 4: reuse ask_number
Lesson 3.5 needed a whole milestone to skip bad guesses. With ask_number, it's one
line.
መልመጃ
The guessing game, with ask_number
Replace the input line with a call to ask_number for a guess from 1 to 100. The
Input box includes a typo, abc, which shouldn't count as an attempt.
Your code
import random
def ask_number(prompt, low, high):
while True:
text = input(prompt)
if text.isdigit() and low <= int(text) <= high:
return int(text)
print(f"Please enter a number from {low} to {high}.")
def main():
random.seed(2019)
secret = random.randint(1, 100)
attempts = 0
while True:
guess = int(input("Guess: "))
attempts = attempts + 1
if guess > secret:
print("Too high")
elif guess < secret:
print("Too low")
else:
print(f"You got it in {attempts} attempts!")
return
if __name__ == "__main__":
main()In the editor, press Escape then Tab to move on.
One answer per line, given to input() in order.
መፍትሄውን አሳይHide the solution
This is one way to solve it, not the only one. If yours prints the same thing, it works.
import random
def ask_number(prompt, low, high):
while True:
text = input(prompt)
if text.isdigit() and low <= int(text) <= high:
return int(text)
print(f"Please enter a number from {low} to {high}.")
def main():
random.seed(2019)
secret = random.randint(1, 100)
attempts = 0
while True:
guess = ask_number("Guess: ", 1, 100)
attempts = attempts + 1
if guess > secret:
print("Too high")
elif guess < secret:
print("Too low")
else:
print(f"You got it in {attempts} attempts!")
return
if __name__ == "__main__":
main()main() never sees abc, so it never counts it. Delete the seed line to play for real.
Milestone 5: the Ethiopian calendar (stretch)
From lesson 2.2: 12 months of 30 days, then Pagume with 5 days, or 6 in a leap year,
when year % 4 == 3. Two small functions, and the same kind of checks as milestone 1.
መልመጃ
is_leap and days_in_month
Write is_leap(year) returning True or False, and days_in_month(month, year)
that uses it. Make the checks pass.
Your code
def is_leap(year):
pass
def days_in_month(month, year):
pass
checks = [(1, 2019, 30), (12, 2020, 30), (13, 2019, 6), (13, 2020, 5), (13, 2023, 6)]
for month, year, expected in checks:
if days_in_month(month, year) != expected:
print(f"Wrong for month {month} of {year}: got {days_in_month(month, year)}, expected {expected}")
print("All checks done")In the editor, press Escape then Tab to move on.
መፍትሄውን አሳይHide the solution
This is one way to solve it, not the only one. If yours prints the same thing, it works.
def is_leap(year):
return year % 4 == 3
def days_in_month(month, year):
if month <= 12:
return 30
if is_leap(year):
return 6
return 5
checks = [(1, 2019, 30), (12, 2020, 30), (13, 2019, 6), (13, 2020, 5), (13, 2023, 6)]
for month, year, expected in checks:
if days_in_month(month, year) != expected:
print(f"Wrong for month {month} of {year}: got {days_in_month(month, year)}, expected {expected}")
print("All checks done")year % 4 == 3 is already True or False, so is_leap returns it with no if.
ዋና ዋና ነጥቦች
- Give each function one job, and a name that says it:
grade_for,ask_number,main. - Functions that return are easy to test: compare the result with what it should be. Functions that print aren't.
- Keep asking and printing in
main(); keep the rules in functions that only calculate. - A function that takes its limits as parameters, like
ask_number(prompt, low, high), can be reused in other programs. if __name__ == "__main__": main()runs the program when the file is run.