Each practical from now on, you will do your work in a new branch, then do a pull request, mentioning a new/different student to get (and give) a code review.
Find the right student to mention here each week (different for each practical)
You do not need to wait to receive your code review before starting.
If you have not received a review, you can still complete this process.
The PR URL is still the same and the reviewer can still review the code even though it has been merged.
Let's run through the process:
- Merge your PR on GitHub.
This updates your remotemaster
branch to contain the work from your last feedback branch. - In PyCharm, Checkout your local
master
branch. - Pull to update your local
master
branch. This pulls in the changes from GitHub including the merge. - (Double-check that you are on the
master
branch, not the feedback branch.) - Create a new branch,
prac_06_feedback
. - Do your prac work.
Expectations and marking for this prac include both requesting and doing a code review.
See submission details at the end of the prac.
We have seen how to work with lists, tuples and dictionaries to store and process data appropriate for those types:
list
is useful for storing an ordered sequence of data (e.g., monthly rainfall data)tuple
is useful for storing fixed (not changing) data with multiple parts (e.g., date of birth)dict
is useful when the data has a 'mapping' relationship (e.g., name -> date of birth)
Very often we want to combine data into one object in a way that does not suit one of the built-in types, so we write our own classes for these situations. That's what this practical is all about. As you do the prac, pay attention to how the class construct helps us combine data and functions in one entity. Ask questions as needed!
First, please confirm that you are on an up-to-date feedback branch with all your previous work. OK?
Get (remember to click Raw) car.py and used_cars.py and add them to your PyCharm project in this week's prac folder.
The import
statement assumes you have your car.py
file in a folder called prac_06
as we suggested.
Run your program and it should work.
Spend some time studying the Car class.
In the used_cars program file, write one new line of code for each of the following:
-
Create a new Car object called "limo" that is initialised with 100 units of fuel.
-
Add 20 more units of fuel to this new car object using the
add
method. -
Print the amount of fuel in the car.
-
Attempt to drive the car 115 km using the
drive
method. -
Now add the
__str__
method to the Car class incar.py
.
Using f-string formatting. Make it return a string in the following format:
Car, fuel=42, odometer=277
Remember that you can run this method by printing your car object, or passing the car object to thestr()
function.
Do NOT call the method verbosely likemy_car.__str__()
-
Now add a
name
field to the Car class (incar.py
), and adjust the__init__
and__str__
methods to set and display this respectively. Make the str method return the car's name instead of just "Car".
Now add names (literals) to the constructors where you create Car objects in theused_cars.py
program.
Test your work and make sure you can now make and view named cars. -
In your
used_cars.py
program, print your car object/s to make sure that the__str__
method is working as expected.
For each of the remaining exercises (as we've done before), read the question, then put an estimate of how long it will
take you in the module docstring.
Record the current time here in your comment.
When you've finished, check the time and record how long it actually took you.
File: programming_language.py
File: languages.py
Let's make our own simple class for a programming language in the file
programming_language.py
Note
Remember that file/module names are always the same as the class but in lower case,
with optional _
for readability.
Follow Python's recommended "CapWords" style and call your class ProgrammingLanguage
.
For each language, we want to store the following fields - the row names from this table, based mostly on the information found at this Programming Language Comparison page.
(Field) | Java | C++ | Python | Visual Basic | Ruby |
---|---|---|---|---|---|
Typing | Static | Static | Dynamic | Static | Dynamic |
Reflection | Yes | No | Yes | No | Yes |
Year | 1995 | 1983 | 1991 | 1991 | 1995 |
Define the following methods:
-
__init__
- like most init functions, create the fields and set them to the parameters passed in. -
is_dynamic()
- which returns True/False if the programming language is dynamically typed or not.
[IMPORTANT] Please understand that this function will take no parameters other than
self
. The data is already inside the object, so you don't need to tell the object its own data.
This function's purpose is to encapsulate the functionality that would make the class more helpful.
See how the function name starts with is
, like isupper()
, isnumeric()
, etc.?
So, it returns a Boolean.
Create a simple program in languages.py
Import the class, then copy these lines into your new program:
python = ProgrammingLanguage("Python", "Dynamic", True, 1991)
ruby = ProgrammingLanguage("Ruby", "Dynamic", True, 1995)
visual_basic = ProgrammingLanguage("Visual Basic", "Static", False, 1991)
print(python)
Now add the __str__
method to the class (not the client code), which should return a string like:
Python, Dynamic Typing, Reflection=True, First appeared in 1991
See if your __str__
function is working properly by running the program to check that printing works as expected.
Now create a new list that contains these three existing ProgrammingLanguage
objects.
Do this next part on paper first, then copy it into PyCharm to see how you went.
Remember that writing code on paper (or a whiteboard) can help you learn better (since you can't
depend on the IDE's help), and encourages you to be consistent and clear with syntax, indenting, etc.
Loop through and print the names of all the languages with dynamic typing (make sure you use your new is_dynamic
method!), which should produce output like:
The dynamically typed languages are:
Python
Ruby
File: guitar.py
Remember the string formatting example from prac 2:
name = "Gibson L-5 CES"
year = 1922
cost = 16035.40
print(f"My guitar: {name}, first made in {year}")
You should notice that we have multiple values to store for one guitar entity: name, year and cost... and that guitars are awesome! What if we owned 9 guitars? We'd want to use a collection like a list... but what would each element in the list be? ... A tuple? A dictionary? No... This is a classic case for a class!
Write a Guitar
class that allows you to store one guitar with these fields (attributes):
name
year
cost
Define the following methods:
-
__init__
- with defaultsname="", year=0, cost=0
-
__str__
- which uses {} string formatting to return something like (using the values from above):
Gibson L-5 CES (1922) : $16,035.40
-
get_age()
- which returns how old the guitar is in years (e.g., in 2022 the L-5 is: 2022 - 1922 = 100) -
is_vintage()
- which returnsTrue
if the guitar is 50 or more years old,False
otherwise
Hint: try usingget_age()
to simplify the implementation of this method!
Remember that methods should not take in any data that the object already knows (like age, year, etc.).
File: guitar_test.py
Now write a program with at least enough code to test that the last two methods work as expected.
To test get_age()
, you could test that the above example guitar does indeed output 100 as
expected. Here's some sample output for testing two guitars where the second is called Another Guitar and has
year=2013:
Gibson L-5 CES get_age() - Expected 100. Got 100
Another Guitar get_age() - Expected 9. Got 9
Gibson L-5 CES is_vintage() - Expected True. Got True
Another Guitar is_vintage() - Expected False. Got False
Do you see how this works?
We print our own literal for what we expect if the function works (e.g., 100), then we print what the method actually
returns, and we look at the output to see if they match.
This form of testing is quite 'manual' since we need to read the output and compare it ourselves, but it is a good
start.
Let's say we wrote the is_vintage()
method incorrectly, then we want to see something like:
50-year old guitar is_vintage() - Expected True. Got False
We can see that the actual does not match the expected, so we know we need to fix something.
File: guitars.py
Got your class working and tested? Great!
Write a program that uses it.
This program should use a list to store all the user's guitars (keep inputting until they enter a blank name), then print their details.
Important
Read the full question including the notes before starting.
We've written helpful guidance to make this easier and to teach you valuable lessons.
Sample Output (bold is user entry):
My guitars! Name: Fender Stratocaster Year: 2014 Cost: $765.4 Fender Stratocaster (2014) : $765.40 added. Name: ... snip ... These are my guitars: Guitar 1: Gibson L-5 CES (1922), worth $ 16,035.40 (vintage) Guitar 2: Line 6 JTV-59 (2010), worth $ 1,512.90 Guitar 3: Fender Stratocaster (2014), worth $ 765.40
When testing a program like this you can waste a lot of time typing in input... then changing something, running it
again and... typing the same thing again...
So don't do it!
Instead, comment out the user input lines, and put in lines like this to 'get' the data for testing:
guitars.append(Guitar("Gibson L-5 CES", 1922, 16035.40))
guitars.append(Guitar("Line 6 JTV-59", 2010, 1512.9))
According to Wikipedia's page on the abstraction principle:
"When read as recommendation to the programmer, the abstraction principle can be generalised as the "don't repeat yourself" principle, which recommends avoiding the duplication of information in general, and also avoiding the duplication of human effort involved in the software development process."
-
The sample output uses some nice string formatting. Feel free to try and figure this out, or just use something like our code below (the width we use for
guitar.name
is just a guesstimate):print(f"Guitar {i}: {guitar.name:>20} ({guitar.year}), worth ${guitar.cost:10,.2f}{vintage_string}")
The variable
vintage_string
is set to""
or" (vintage)"
depending on theis_vintage()
method.
If you're keen, try using Python's ternary operator to do this in one line.
E.g., to set the value ofcategory
toadult
orchild
depending onage
, you could use:category = "adult" if age >= 18 else "child"
-
For this particular code, we've used both
i
and the target variableguitar
(instead ofguitars[i]
) by using the built-inenumerate()
function.for i, guitar in enumerate(guitars, 1): # do something with i (the index) and guitar (the element)
We are just past halfway, so it's helpful to stop and reflect, and adjust as needed.
Download and save the template markdown file REFLECTION.md to your prac_06
folder.
Write short but meaningful answers to the questions in that file.
Use these exercises as much-needed practice and as ways to learn new things.
Similar to the practice question with friends' names and addresses in prac 5,
create a program that uses a list of Person
objects.
Each Person
object includes first-name, last-name and age.
The user can type in the details of any number of people. The code generates a table formatted with the first-names,
last-names, and ages of the people (perhaps sort the people into order based on their ages).
Create a car driving simulator in car_simulator.py
that uses the Car
class that works like the following sample
output...
Note
Please do this (and every problem of significant size) incrementally:
- Start by just testing one method call,
- then another,
- then write the menu and put it all together.
Remember to use the class's functionality.
Don't rewrite anything you've already got.
Do you remember how to construct a simple menu-driven program? If not, revise earlier lectures and practicals.
You will need to import
the car
module, create a Car
object, and use appropriate methods on that object.
Let's drive!
Enter your car name: The bomb
The bomb, fuel=100, odo=0
Menu:
d) drive
r) refuel
q) quit
Enter your choice: f
Invalid choice
The bomb, fuel=100, odo=0
Menu:
d) drive
r) refuel
q) quit
Enter your choice: d
How many km do you wish to drive? 39
The car drove 39km.
The bomb, fuel=61, odo=39
Menu:
d) drive
r) refuel
q) quit
Enter your choice: d
How many km do you wish to drive? -25
Distance must be >= 0
How many km do you wish to drive? 100
The car drove 61km and ran out of fuel.
The bomb, fuel=0, odo=100
Menu:
d) drive
r) refuel
q) quit
Enter your choice: r
How many units of fuel do you want to add to the car? -80
Fuel amount must be >= 0
How many units of fuel do you want to add to the car? 120
Added 120 units of fuel.
The bomb, fuel=120, odo=100
Menu:
d) drive
r) refuel
q) quit
Enter your choice: d
How many km do you wish to drive? 25
The car drove 25km.
The bomb, fuel=95, odo=125
Menu:
d) drive
r) refuel
q) quit
Enter your choice: q
Good bye The bomb's driver.
Note
Python has good modules for dates, like date
and datetime
.
You would usually use these, not write your own. This is a practice question.
Create a Date
class, storing the fields:
day
month
year
Write some useful methods, including:
__init__
and__str__
add_days(n)
- which should add n days to the stored date (perhaps harder than it seems)
Test the class.
It's a valuable skill! On the lab computers, you should be able to use "Git Bash". Right-click on the folder where
your files are and select "Git Bash here".
On a Mac, just use Terminal.
Why? Here's a quote from one of our students who completed this subject:
"Now I'm moving into 2nd yr subjects, I found the integrated git/pycharm a good intro step, but I'm finding git bash with a simple cheat sheet is a nice progression. I think 99% of us could use some more training/practice with branching, merging, etc. Still kinda confusing in the terminology."
The more you have to do by yourself, the more you will really understand what's happening. Some things (like removing a file from the index) cannot be done with the IDE integration. So, as soon as you are happy to, start getting used to using git from a console.
Create a new repo with git init
, add some code, add files with git add
, commit with git commit
...
Then, next time you're ready to commit and push your practical work, try using the command line to do it!
This section summarises the expectations for marking in this practical.
Please follow the submission guidelines to ensure you receive marks for your work.
You may not receive your code review request in time to submit each week,
so we ask for the code review you did from the previous week.
This week, please submit two PR URLs.
- Type the URL of your Pull Request (PR) that mentions another student for this prac.
- Type the URL of the PR that you reviewed for the previous prac.
car.py
modificationsused_cars.py
modifications (cars have names)programming_language.py
(the class),languages.py
(the client program)guitar.py
(the class),guitar_test.py
(the tests),guitars.py
(the client program)REFLECTION.md