Write your answers for the early non-coding questions in a simple text file called questions.txt
.
This practical builds on the Functions 1 and 2 lectures as well as practical 6.
If you haven't learned from those, then go back and do so before continuing with this practical work.
- Where should global constants be placed in a program?
- What are two reasons why you should not use global variables?
- When you have two variables with the same name but in different functions, these said to have different ____?
- What do you call the red dot in the margin in PyCharm that pauses the program running when using the debugger?
- Briefly explain what would be wrong if you had a line of code like
SOME_VALUE += 1
(assume thatSOME_VALUE
is actually a meaningful name). - What is the single most important design principle for designing functions?
Read the following out loud:
NORTH QUEENSLAND
IN THE
THE SPRING
Good job.
Or was it? Did you say it correctly?
If you didn't... notice that your attention to detail could be improved.
This is a fairly common issue, but one that programmers need to deal with more so than 'regular' people.
You need to continue to systematically build your attension to detail ;)
To answer this question, just write whether you read the statement correctly the first time or not.
This exercise is intended to help you prepare for your second assignment where you will keep a
developer's journal.
In your journal, you will keep track of your work over time, noting any problems and how you overcame them.
You will also complete a summary that identifies any lessons you have learned through the process.
Now, take a couple of minutes and think about your work, how you overcame problems, and particularly your process for practicals in this subject so far...
Write 3 lessons you have learned about yourself and the problem-solving process (not about Python code)
through completing the practicals up to now.
Personal lessons from reflection like this must be personal.
Don't state "truisms" like,
"It's good to read the instructions carefully" or "I've learned to break a larger problem into smaller parts".
Those sorts of lessons could be written at the start of the subject, so are not personal reflections.
When we mark your reflections in the developer journal, we need to see specific, lessons based on your
personal experience.
Debugging is the process of finding and fixing problems in code (yours or someone else's), and is an important skill to
develop.
IMPORTANT: Debugging is not refactoring, or improving working code.
We are not looking for things like formatting issues or unclear variable naming. We don't want nicer code, we want
working code. The problems with the provided code are bugs.
A poor variable name is not good, but it's not a bug.
Don't look for ways to improve the code... look for what's actually wrong with it... why it does not work properly.
Like our logic exercises, it's not just about quickly finding the answer, but about learning to systematically figure it
out.
It's great if you spot/fix an issue straight away, but what if you know there's a problem and you can't see it
immediately?
You need to develop attention to detail and be able to use debugging tools to help you.
In the "Functions 2" lecture, we introduced the PyCharm debugger.
(Again, if you haven't watched that lecture, please stop now and go back to the lecture!)
Note
PyCharm does wonderful "on the fly" debugging by highlighting potential problems in your code.
Get used to noticing the underlines and other warnings and correcting these issues.
Right-click on your prac_07
folder (that's what you called it, right?)
and create a new Python file here called debugging.py
Copy the raw version of the code found at the link below and paste into your file...
Click on the link below, then click the Raw button to get a version suitable for copying.
Don't just copy from the normal view, without clicking Raw, or you will not get proper formatting. OK?
Let's use the debugger now to systematically figure out what's wrong with the following programs.
In all cases, read the code and try and understand it, then use the debugger to "step through" the program.
Follow the arrows in this screenshot to:
- Create a breakpoint on the first line that should run (click in the margin to create breakpoints).
- Right-click your code and choose "Debug debugging".
- The program will run until it reaches the breakpoint. Click the "Step Into My Code" button to run line by line (click it once per line).
- The debugger will stop responding when user input is being asked for, so click (4) to switch to the Console and enter your input.
- The debugger will continue to the next line, so switch back (5) to the Debugger window to see the state of your variables. Notice also how it displays when your code is inside other functions.
Keep clicking through "Step Into My Code" to see exactly how the program runs and what the values of variables are.
You will need to switch between the "Console" (to see output and enter input) and "Debugger" (to see program state)
windows.
There are four "main" programs in debugging.py
for you to debug separately (use a new breakpoint at the start of each)
.
Test/debug each, one at a time.
When you've finished one, "comment out" its main call and "uncomment" the next one.
Example, when testing main_3
, the bottom of your code would look like:
# main()
# main_2()
main_3()
# main_4()
For each program, write what you found to be the problem (not the solution) in the provided section
of debugging,py
, then write the fixed, working, code (solution) in the section below that.
# Problem(s) for program 1:
# ?
# Fixed code for program 1:
Note
"Problem" and "solution" are different things.
Example, if restarting your PC makes it run faster, that's just a (temporary) solution, not the problem that caused
it to run slowly in the first place.
When you have found the problem, you should be able to fix it, so change the code to make it work.
You do not need to explain your solutions, just fix the code and submit the fixed file, debugging.py
.
If you haven't already, copy the code from debugging.py and get debugging!
For each of your functions from now on, write a brief function docstring.
Docstrings, like all comments, should be written in the imperative mood, e.g., "Print" not "Prints".
Here's a good example docstring for a function that checks to see if a password is valid:
def is_valid_password(password):
"""Determine if password is considered valid or not."""
if len(password) < 6 or " " in password:
return False
return True
Download and study the sample code and provided comments here: menu_with_functions.py.
This example shows a familiar program (our number guessing game) using a menu and functions.
Use this example as a reference for writing your assignment and other code.
It demonstrates good function design, parameter passing, pseudocode for functions, docstrings...
Add one more menu and function, so the user can choose (D)isplay
, to display all of the numbers between low
and high
.
Use a function to do this. You know the function needs parameters passed in.
If the low and high are 1 and 10, the function should print:
1 2 3 4 5 6 7 8 9 10
File: jerry.py
In the Functions 2 lecture, you had a "Do this now" question like:
Jerry's car's speedo shows miles (mph) instead of kilometres per hour (kph). He wants to be able to enter his speed in mph, the speed limit in kph and determine his speeding fine.
Copy and use the following pseudocode for your main function:
function main()
speed_in_miles = get_valid_number("Speed in miles")
speed_limit_in_kilometres = get_valid_number("speed limit in kilometres")
speed_in_kilometres = convert_miles_to_kilometres(speed_in_miles)
amount_over_limit = speed_in_kilometres - speed_limit_in_kilometres
fine = determine_fine(amount_over_limit)
print fine
Implement that pseudocode in Python, including the function definitions with proper docstrings.
Remember that you've done some of this before, so you can copy your previous work:
Test this using meaningful test data that you can understand.
Important: The value of the fine as a number type is much better to return than a string, or a whole message.
What if we wanted to determine a new bank balance after paying the fine?
This is an example of SRP.
It is the determine_fine
function's ONLY (single responsibility) job to
determine the actual fine as an actual number.
It is counter-productive for this function to print the fine or to format with a '$' or anything else.
bank_balance -= fine
doesn't work if fine
is something like Your fine is $199
.
< insert "you had one job" meme here ;) >
When you have this working, complete a more fulsome program that matches the following example output:
Your speed in miles per hour: 55.1
Speed limit in km/h: 80
Your speed of 88.7 km/h was over the limit by 8.7 km/h.
Your fine will be $309.00
Enter your current bank balance: $22.35
Your bank balance after your fine will be $-286.65
Don't speed again, OK?
Farewell
and
Your speed in miles per hour: 34
Speed limit in km/h: 60
Your speed of 54.7 km/h has not exceeded the speed limit - no fine :)
Farewell
File: dog_years.py
Below is part of a program to calculate a dog's age in dog years.
Rewrite this program using a function for the calculation.
Remember SRP, the calculation function will receive a value via a parameter and return
the result.
It will not do user input or output.
Write a main
function that repeatedly asks the user for an age in human years, then displays it in dog years until the
user enters a negative number.
Don't do any error checking on this age.
Here's the calculation part for you:
if human_years <= 2:
dog_years = human_years * 10.5
else:
dog_years = 21 + 4 * (human_years - 2)
File: seconds.py
In Prac 2 you wrote a program to calculate and display deep
sleep time/percentage.
In that program, you displayed seconds as minutes and seconds, example:
Total sleep in seconds: 161
Deep sleep in seconds : 62
Deep sleep : 1m 2s
Total sleep: 2m 41s
Percentage : 38.50931677018634%
Note: the original program from prac 2 was about sleep, but this question is not related to that context.
The technique for figuring out minutes and seconds from just seconds is the same, so you can copy your work, but please
change any references to the old context.
Write a simple program using a function that takes in a number of seconds and returns a string that can be used
to display that value in minutes and seconds.
Notice that this function DOES NOT PRINT.
We want to use the string in different ways, so this function's job is simply to create/return a formatted string.
Write a main program that displays a bunch of different seconds values in minutes and seconds using a loop.
Example output:
Note: Think about how this was generated...
There's no user input; it starts at 0, goes to ? in steps of ?
Try and do the same in your program.
0 seconds is 0m 0s
635 seconds is 10m 35s
1270 seconds is 21m 10s
1905 seconds is 31m 45s
2540 seconds is 42m 20s
3175 seconds is 52m 55s
To help you understand SRP even more, let's think about what this function should return
.
Should it return something like?
635 seconds is 10m 35s
Well, what if we wanted to use the same function for different kinds of tasks?
Let's do that now...
Add another small part to this same program - after the loop - that asks the user for their favourite duration in seconds, then prints it in minutes and seconds, like
Favourite duration in seconds: 639
You love 10m 39s
So... if our function returned something like 635 seconds is 10m 35s
, then we could NOT use it for this task, even
though it's really similar.
Do we need a second function that returns something like You love ...
?
NO! That would be repeating ourselves, and we know... DRY.
So... we need to remember SRP.
This function has one job, and it's not printing or returning n seconds is ...
, it's ONLY the bit that
formats the seconds (argument) in minutes and seconds...
which we can now reuse in different situations!
SRP leads to function reusability and helping us not repeat ourselves :).
File: bmis.py
In Prac 6 we wrote a program to calculate a person's BMI and weight category using functions.
Copy those functions (not main) and write a new main program with a loop that runs through a number of weights for a 1.75m person, as below.
Note: You can limit precision of a float using something like:
value = 3.1415926535
print(f"{value:.1f}") # prints 3.1
The :
separates the variable name (value) from the "format specifier".
In this case, .1f
means to produce only 1 decimal place for our float.
Another common format specifier is just a number, like:
value = 17
print(f"!{value:4}!") # prints ! 17! (that is, 17 takes up 4 spaces)
OK, let's do this:
Height 1.75m, Weight 50kg = BMI 16.3, considered underweight
Height 1.75m, Weight 52kg = BMI 17.0, considered underweight
Height 1.75m, Weight 54kg = BMI 17.6, considered underweight
Height 1.75m, Weight 56kg = BMI 18.3, considered underweight
Height 1.75m, Weight 58kg = BMI 18.9, considered normal
Height 1.75m, Weight 60kg = BMI 19.6, considered normal
Height 1.75m, Weight 62kg = BMI 20.2, considered normal
Height 1.75m, Weight 64kg = BMI 20.9, considered normal
Height 1.75m, Weight 66kg = BMI 21.6, considered normal
Height 1.75m, Weight 68kg = BMI 22.2, considered normal
Height 1.75m, Weight 70kg = BMI 22.9, considered normal
Height 1.75m, Weight 72kg = BMI 23.5, considered normal
Height 1.75m, Weight 74kg = BMI 24.2, considered normal
Height 1.75m, Weight 76kg = BMI 24.8, considered normal
Height 1.75m, Weight 78kg = BMI 25.5, considered overweight
Height 1.75m, Weight 80kg = BMI 26.1, considered overweight
Height 1.75m, Weight 82kg = BMI 26.8, considered overweight
Height 1.75m, Weight 84kg = BMI 27.4, considered overweight
Height 1.75m, Weight 86kg = BMI 28.1, considered overweight
Height 1.75m, Weight 88kg = BMI 28.7, considered overweight
Height 1.75m, Weight 90kg = BMI 29.4, considered overweight
Height 1.75m, Weight 92kg = BMI 30.0, considered obese
Height 1.75m, Weight 94kg = BMI 30.7, considered obese
Height 1.75m, Weight 96kg = BMI 31.3, considered obese
Height 1.75m, Weight 98kg = BMI 32.0, considered obese
Height 1.75m, Weight 100kg = BMI 32.7, considered obese
Got it? Good. Now, let's try it with varying heights as well as weights, like:
Hint: 1.5m is 150cm; useful to know since range
only works with integers.
Notice also that the values line up nicely. You don't have to do this, but see if you can.
Height 1.5m, Weight 50kg = BMI 22.2, considered normal
Height 1.5m, Weight 60kg = BMI 26.7, considered overweight
Height 1.5m, Weight 70kg = BMI 31.1, considered obese
Height 1.5m, Weight 80kg = BMI 35.6, considered obese
Height 1.5m, Weight 90kg = BMI 40.0, considered obese
Height 1.5m, Weight 100kg = BMI 44.4, considered obese
Height 1.6m, Weight 50kg = BMI 19.5, considered normal
Height 1.6m, Weight 60kg = BMI 23.4, considered normal
Height 1.6m, Weight 70kg = BMI 27.3, considered overweight
Height 1.6m, Weight 80kg = BMI 31.2, considered obese
Height 1.6m, Weight 90kg = BMI 35.2, considered obese
Height 1.6m, Weight 100kg = BMI 39.1, considered obese
Height 1.7m, Weight 50kg = BMI 17.3, considered underweight
Height 1.7m, Weight 60kg = BMI 20.8, considered normal
Height 1.7m, Weight 70kg = BMI 24.2, considered normal
Height 1.7m, Weight 80kg = BMI 27.7, considered overweight
Height 1.7m, Weight 90kg = BMI 31.1, considered obese
Height 1.7m, Weight 100kg = BMI 34.6, considered obese
Height 1.8m, Weight 50kg = BMI 15.4, considered underweight
Height 1.8m, Weight 60kg = BMI 18.5, considered normal
Height 1.8m, Weight 70kg = BMI 21.6, considered normal
Height 1.8m, Weight 80kg = BMI 24.7, considered normal
Height 1.8m, Weight 90kg = BMI 27.8, considered overweight
Height 1.8m, Weight 100kg = BMI 30.9, considered obese
Height 1.9m, Weight 50kg = BMI 13.9, considered underweight
Height 1.9m, Weight 60kg = BMI 16.6, considered underweight
Height 1.9m, Weight 70kg = BMI 19.4, considered normal
Height 1.9m, Weight 80kg = BMI 22.2, considered normal
Height 1.9m, Weight 90kg = BMI 24.9, considered normal
Height 1.9m, Weight 100kg = BMI 27.7, considered overweight
These final sections in practicals are not required to be completed for marks, but you will definitely find benefit in
completing them for extra practice and to extend yourself.
The more practice you do, the more you develop and "lock in" your new skills.
Create a new file, practice.py
to complete these tasks in:
What other previous practical questions can you rewrite (in new files, not in their existing files!) using functions?
Do as many as you can, making sure to follow the principles you have learned in the lectures, including:
- Good verb-phrase variable naming, "This function will..."
- Single responsibility principle (SRP)
- Testing, including using the function in different ways (e.g., with user input or literals, with output to the screen or as part of an expression)
Update the guessing game in a new file (don't change the existing file) with new features like:
- a high (low!) score
- a way to set the low limit
- a user name
- a maximum value for the high limit (user can't choose above maximum)
- ...
Extend the function you wrote earlier that converts seconds to minutes and seconds so that it can also handle hours. Example:
635 seconds is 0h 10m 35s
4269 seconds is 1h 11m 9s
When you have it working like that, try and use your string formatting so that each h/m/s value takes up 2 spaces and the input seconds line up, like:
635 seconds is 0h 10m 35s
4269 seconds is 1h 11m 9s
7903 seconds is 2h 11m 43s
11537 seconds is 3h 12m 17s
47528 seconds is 13h 12m 8s
When you've done that, try adding more string formatting to other questions in this or recent practicals (as makes sense).
This section summarises the expectations for marking in this practical.
Please follow the submission guidelines to ensure you receive marks for your work.
Files required:
questions.txt
with:
- Quick Questions
- Logic Exercise (just write whether you read the statement correctly the first time)
- Reflection
Exercises, each in their own file:
menu_with_functions.py
(the example) with added display functiondebugging.py
with both problems and solutions (fixed code)jerry.py
dog_years.py
seconds.py
bmis.py