Week 7 Tutorial: Revision
Problem 1 (Easy): Video Game aPlayer Profile
You are building a basic scoring system for a new video game. You need to create a simple class that tracks a player’s username and their current score, and allows them to earn points.
- Define a class named
PlayerProfile. - Create an
__init__method that accepts one parameter:username. - Inside
__init__, store the parameter as an instance variableself.username. Also, initialize another instance variableself.scoreand set it to0. - Create a method named
add_points(self, points)that adds the givenpointsto the player’s current score. - Create a method named
display_status(self)that prints the player’s details in this exact format:"Player: {username}, Score: {score}".
Input
player1 = PlayerProfile("ShadowNinja")
player2 = PlayerProfile("CyberSamurai")
player1.display_status()
player1.add_points(50)
player1.add_points(25)
player2.add_points(100)
player1.display_status()
player2.display_status()
Expected Output
Player: ShadowNinja, Score: 0
Player: ShadowNinja, Score: 75
Player: CyberSamurai, Score: 100
Problem 2 (Easy): Gift Card Manager
You are developing a payment system and need to create a class for digital gift cards. The gift card’s code should be protected from changes, and its balance must never drop below zero.
- Define a class named
GiftCardwith an__init__method that acceptscodeandbalance. - Store
codein a protected attribute_codeand create a read-only@propertygetter for it (do not create a setter forcode). - Create a
@propertygetter forbalancethat returns a protected attribute_balance. - Create a
@balance.setterthat raises aValueErrorwith the message"Balance cannot be negative"if the given value is less than 0. Otherwise, store the value in_balance. - Inside
__init__, useself.balance = balanceto ensure the initial balance is validated by your setter. - Create a method
spend(self, amount)that reduces the balance by the given amount. If the amount is greater than the current balance, do not change the balance; instead, raise aValueErrorwith the message"Insufficient funds".
Input
card = GiftCard("AMZ-123", 50.0)
print(card.code)
print(card.balance)
card.spend(20.0)
print(card.balance)
try:
card.spend(100.0)
except ValueError as e:
print(e)
try:
card.balance = -10.0
except ValueError as e:
print(e)
try:
card.code = "NEW-CODE"
except AttributeError:
print("Cannot change code")
Expected Output
AMZ-123
50.0
30.0
Insufficient funds
Balance cannot be negative
Cannot change code
Problem 3 (Medium): Recipe Ingredient Combiner
You are building a digital cookbook. The system needs to manage recipes and automatically combine duplicate ingredients (e.g., if a recipe asks for “200g of Flour” and later asks for “100g of Flour”, it should automatically become “300g of Flour”).
- Define an
Ingredientclass with an__init__that takesname(string),amount(number), andunit(string). - Implement
__str__(self)to return the format"{amount}{unit} of {name}"(e.g.,"200g of Flour"). - Implement
__add__(self, other):- Use
isinstance()to ensureotheris anIngredient. - If
otheris anIngredientand has the exact samenameandunitas the current object, return a newIngredientwith the amounts added together. - Otherwise, return
NotImplemented.
- Use
- Define a
Recipeclass (which will use composition to hold ingredients) with an__init__(self, name)that stores the recipe name and an empty list of ingredients. - Implement
add_ingredient(self, ingredient):- Check if an ingredient with the exact same name and unit already exists in the recipe’s list.
- If it does, replace the existing ingredient by combining it with the new one (use the
+operator to utilize your__add__method). - If it does not exist, simply append it to the list.
- Implement
__len__(self)for theRecipeclass to return the number of distinct ingredients. - Implement
display(self)inRecipethat prints"Recipe: {name}"followed by each ingredient on a new line, indented with" - ".
Input
flour1 = Ingredient("Flour", 200, "g")
flour2 = Ingredient("Flour", 100, "g")
sugar = Ingredient("Sugar", 50, "g")
water = Ingredient("Water", 1, "cup")
print(flour1 + flour2)
try:
print(flour1 + water)
except TypeError:
print("Cannot add different ingredients")
bread = Recipe("Simple Bread")
bread.add_ingredient(flour1)
bread.add_ingredient(water)
bread.add_ingredient(flour2) # Should automatically merge with flour1
bread.add_ingredient(sugar)
print(f"Total distinct ingredients: {len(bread)}")
bread.display()
Expected Output
300g of Flour
Cannot add different ingredients
Total distinct ingredients: 3
Recipe: Simple Bread
- 300g of Flour
- 1cup of Water
- 50g of Sugar
Problem 4 (Medium): Smart Fleet Dispatcher
You are building a dispatch system for a modern courier service that utilizes both drones and traditional cargo vans. You need to use an abstract base class to enforce a strict contract for all vehicles, inheritance for specific vehicle types, and composition to manage the overall fleet.
- Import
ABCandabstractmethodfrom theabcmodule. - Define an abstract base class
DeliveryVehiclethat inherits fromABC.__init__(self, vehicle_id): stores thevehicle_id.@abstractmethod: define an abstract methodestimated_time(self, distance).- Define a regular method
describe(self)that returns the string"<ClassName> <vehicle_id>"(Hint: useself.__class__.__name__to dynamically get the class name).
- Create a subclass
Dronethat inherits fromDeliveryVehicle.- Implement
estimated_time(distance): Drones travel at 50 km/h, so returndistance / 50.0.
- Implement
- Create a subclass
CargoVanthat inherits fromDeliveryVehicle.- Override
__init__(self, vehicle_id, loading_time): usesuper()to initialize the parent, and storeloading_time(in hours) as an instance variable. - Implement
estimated_time(distance): Vans travel at 80 km/h, but they require time to load the cargo. Return(distance / 80.0) + self.loading_time.
- Override
- Create a
CourierServiceclass (representing composition).__init__(self, name): stores the servicenameand initializes an empty listself.fleet.add_vehicle(self, vehicle): appends the vehicle to the fleet.get_fastest_vehicle(self, distance): iterate throughself.fleetusing polymorphism. Callestimated_time(distance)on each vehicle to find the one with the lowest time. Print"Dispatching <description> - ETA: <time> hours", and return that vehicle object. (Assume the fleet is never empty).
Input
service = CourierService("Express City Delivery")
drone1 = Drone("D-01")
van1 = CargoVan("V-100", loading_time=0.5)
van2 = CargoVan("V-101", loading_time=0.2)
service.add_vehicle(drone1)
service.add_vehicle(van1)
service.add_vehicle(van2)
fastest_short = service.get_fastest_vehicle(10)
fastest_long = service.get_fastest_vehicle(100)
try:
vehicle = DeliveryVehicle("X-01")
except TypeError:
print("Cannot instantiate abstract class")
Expected Output
Dispatching Drone D-01 - ETA: 0.2 hours
Dispatching CargoVan V-101 - ETA: 1.45 hours
Cannot instantiate abstract class
Problem 5 (Advanced): Secure Notification Broadcaster
You are building a notification system for an IT department. The system must enforce a strict contract for all message senders, keep an audited log of every transmission using decorators, and support different initialization formats using class methods.
- Import
ABCandabstractmethodfrom theabcmodule. - Write a decorator
audit_log(func)that defines awrapper(*args, **kwargs)inside. The wrapper should print"[AUDIT] Calling {func.__name__}", call the original function and capture its result, print"[AUDIT] Success", and finally return the captured result. - Define an abstract base class
MessageSenderinheriting fromABC.- Create a class variable
_total_messagesset to0. - Define an
@abstractmethodcalledsend(self, text). - Define a
@classmethodcalledget_total(cls)that returns the value of_total_messages.
- Create a class variable
- Create a subclass
EmailSenderinheriting fromMessageSender.__init__(self, email_address)stores the email.- Override
send(self, text)and decorate it with@audit_log. Inside, incrementMessageSender._total_messagesby1, and return the string"Email to {email_address}: {text}". - Define a
@classmethodcalledfrom_contact(cls, contact_string)that takes a string formatted like"Contact:student@mail.com", extracts the email part, and returns a newEmailSenderobject. - Define a
@staticmethodcalledis_valid_email(email)that returnsTrueif the string contains an"@", andFalseotherwise.
- Create a subclass
SMSSenderinheriting fromMessageSender.__init__(self, phone_number)stores the phone number.- Override
send(self, text)and decorate it with@audit_log. Inside, incrementMessageSender._total_messagesby1, and return the string"SMS to {phone_number}: {text}".
- Write a standalone function
broadcast(senders, text)that accepts a list of sender objects (polymorphism) and a message string. Iterate through the list, callsend(text)on each object, and print the returned string.
Input
print(f"Valid email? {EmailSender.is_valid_email('bad-email')}")
print(f"Valid email? {EmailSender.is_valid_email('good@email.com')}")
email1 = EmailSender("alisher@akhu.uz")
email2 = EmailSender.from_contact("Contact:sevara@akhu.uz")
sms1 = SMSSender("+998901234567")
senders = [email1, email2, sms1]
broadcast(senders, "Server maintenance at midnight.")
print(f"Total messages sent: {MessageSender.get_total()}")
try:
sender = MessageSender()
except TypeError:
print("Cannot instantiate abstract class")
Expected Output
Valid email? False
Valid email? True
[AUDIT] Calling send
[AUDIT] Success
Email to alisher@akhu.uz: Server maintenance at midnight.
[AUDIT] Calling send
[AUDIT] Success
Email to sevara@akhu.uz: Server maintenance at midnight.
[AUDIT] Calling send
[AUDIT] Success
SMS to +998901234567: Server maintenance at midnight.
Total messages sent: 3
Cannot instantiate abstract class
Problem 6 (Advanced): Digital Media Ecosystem
You are building the core engine for “NetStream”, a digital media platform. You must design a robust system that handles media pricing validation, supports polymorphism for playing different media types, logs user actions using decorators, and allows users to combine their libraries using magic methods.
Follow these strict design requirements to build out the system:
- Decorator (Week 6):
- Create a decorator
audit_action(func)that defines a wrapper. The wrapper should print"[AUDIT] Action: <function_name>"(usingfunc.__name__), call the original function, and return its result.
- Create a decorator
- Abstract Base Class (Week 5 & Week 2):
- Create an abstract class
Mediainheriting fromABC. __init__(self, title, price): storestitleand initializesprice.- Protect the price using a
@propertygetter. - Create a
@price.setterthat raises aValueErrorwith the message"Price cannot be negative"if the value is less than 0. (Ensure the__init__routes through this setter). - Define an
@abstractmethodcalledconsume(self). - Define the magic method
__str__(self)to return the format"<title> ($<price>)".
- Create an abstract class
- Subclasses (Week 4):
- Create
Movie(Media):__init__adds aduration(in minutes). Overrideconsume()(decorate it with@audit_action) to return"Watching <title> (<duration>m)". - Create
Song(Media):__init__adds anartist. Overrideconsume()(decorate it with@audit_action) to return"Listening to <title> by <artist>".
- Create
- Composition & Magic Methods (Week 3 & 4):
- Create a
Libraryclass with__init__(self, owner)that stores the stringownerand an empty listitems. add_item(self, media): appends toitems.__len__(self): returns the number of items in the library.__add__(self, other): Ifotheris aLibrary, return a newLibrarywith the owner named"<owner1> & <owner2>"and anitemslist containing all media from both libraries. Otherwise, returnNotImplemented.play_all(self): iterates throughitems, callsconsume()on each, and prints the returned string.
- Create a
- Class & Static Methods (Week 1 & 6):
- Create a
Userclass with a class variableplatform_name = "NetStream". __init__(self, username, balance): stores the attributes and initializesself.libraryas a newLibrarybelonging to this username.purchase(self, media): Ifbalanceis greater than or equal tomedia.price, deduct the price and add the media to the user’s library. Otherwise, raiseValueError("Insufficient funds").@classmethod from_string(cls, data): accepts a string like"Username:Balance", splits it, converts balance to a float, and returns a newUserobject.@staticmethod is_valid_username(name): returnsTrueif the username is 5 or more characters long, otherwiseFalse.
- Create a
Input
print(f"Valid username 'Ali': {User.is_valid_username('Ali')}")
print(f"Valid username 'Alisher': {User.is_valid_username('Alisher')}")
user1 = User("Nodira", 20.0)
user2 = User.from_string("Jasur:15.0")
movie1 = Movie("Inception", 12.0, 148)
song1 = Song("Shape of You", 2.0, "Ed Sheeran")
song2 = Song("Blinding Lights", 3.0, "The Weeknd")
try:
bad_movie = Movie("Bad Movie", -5.0, 90)
except ValueError as e:
print(f"Error: {e}")
user1.purchase(movie1)
user1.purchase(song1)
try:
user1.purchase(movie1)
except ValueError as e:
print(f"Purchase failed: {e}")
user2.purchase(song2)
print(f"{user1.username}'s library has {len(user1.library)} items.")
shared_library = user1.library + user2.library
print(f"Shared Library Owner: {shared_library.owner}")
print(f"Shared Library Size: {len(shared_library)}")
shared_library.play_all()
Expected Output
Valid username 'Ali': False
Valid username 'Alisher': True
Error: Price cannot be negative
Purchase failed: Insufficient funds
Nodira's library has 2 items.
Shared Library Owner: Nodira & Jasur
Shared Library Size: 3
[AUDIT] Action: consume
Watching Inception (148m)
[AUDIT] Action: consume
Listening to Shape of You by Ed Sheeran
[AUDIT] Action: consume
Listening to Blinding Lights by The Weeknd
Problem 7 (Advanced): Online Exam System
You are building the backend for an online examination platform called “QuizMaster”. The system must manage different question types, grade them automatically, track attempts with a decorator, and allow merging of exam sections using magic methods.
Follow these strict design requirements:
- Decorator (Week 6):
- Create a decorator
track_attempt(func)that defines a wrapper. The wrapper should print"[ATTEMPT] Answering question...", call the original function and capture the boolean result, print"[ATTEMPT] Correct!"if the result isTrueor"[ATTEMPT] Wrong!"ifFalse, and return the result.
- Create a decorator
- Abstract Base Class (Week 5 & Week 2):
- Create an abstract class
Questioninheriting fromABC. __init__(self, text, points): storestextand initializespoints.- Protect
pointsusing a@propertygetter. - Create a
@points.setterthat raises aValueErrorwith the message"Points must be positive"if the value is less than or equal to 0. (Ensure__init__routes through this setter). - Define an
@abstractmethodcalledcheck_answer(self, answer). - Define
__str__(self)to return"[<points>pts] <text>".
- Create an abstract class
- Subclasses (Week 4):
- Create
MultipleChoice(Question):__init__addsoptions(a list of strings) andcorrect_index(an integer, the index of the correct option in the list). Overridecheck_answer(self, answer)(decorate with@track_attempt): theanswerparameter is an integer representing the chosen option’s index. Compare it against the stored correct index and return the result. - Create
FillInTheBlank(Question):__init__addscorrect_answer(a string). Overridecheck_answer(self, answer)(decorate with@track_attempt): compare the given answer to the stored correct answer in a case-insensitive way, ignoring leading and trailing spaces, and return the result.
- Create
- Composition & Magic Methods (Week 3 & 4):
- Create an
Examclass with__init__(self, title)that storestitleand an empty listquestions. add_question(self, question): appends toquestions.__len__(self): returns the number of questions.__add__(self, other): Ifotheris anExam, return a newExamwith the title"<title1> + <title2>"and aquestionslist containing all questions from both exams. Otherwise, returnNotImplemented.total_points(self): returns the sum ofpointsfor all questions in the exam.grade(self, answers): accepts a list of answers (same length asquestions). Iterates through each question, callscheck_answerwith the corresponding answer, sums up the points for correct answers, and returns the total earned points.
- Create an
- Class & Static Methods (Week 1 & 6):
- Create a
Studentclass with a class variablepassing_percentage = 60. __init__(self, name, student_id): stores the attributes and initializesself.scoresas an empty dictionary (keys will be exam titles, values will be tuples of(earned, total)).take_exam(self, exam, answers): callsexam.grade(answers)to get the earned points, stores(earned, exam.total_points())inself.scoresunder the exam’s title, and returns the earned points.passed(self, exam_title): looks up the exam title inself.scores, calculates the percentage(earned / total) * 100, and returnsTrueif it is greater than or equal topassing_percentage, otherwiseFalse.@classmethod from_string(cls, data): accepts a string like"Name:ID", splits it, and returns a newStudentobject.@staticmethod is_valid_id(student_id): returnsTrueif the string starts with"S-"and is exactly 5 characters long (e.g.,"S-001"), otherwiseFalse.
- Create a
Input
print(f"Valid ID 'S-001': {Student.is_valid_id('S-001')}")
print(f"Valid ID '12345': {Student.is_valid_id('12345')}")
try:
bad_q = MultipleChoice("Bad?", -2, ["A", "B"], 0)
except ValueError as e:
print(f"Error: {e}")
q1 = MultipleChoice("What is the capital of France?", 10,
["London", "Paris", "Berlin", "Madrid"], 1)
q2 = FillInTheBlank("The keyword to define a function in Python is ___", 10,
"def")
q3 = MultipleChoice("Which data structure uses FIFO?", 5,
["Stack", "Queue", "Tree", "Graph"], 1)
q4 = FillInTheBlank("Python lists are ___ (mutable/immutable)", 5,
"mutable")
print(q1)
print(q2)
midterm = Exam("Midterm")
midterm.add_question(q1)
midterm.add_question(q2)
quiz = Exam("Pop Quiz")
quiz.add_question(q3)
quiz.add_question(q4)
final = midterm + quiz
print(f"Final exam: '{final.title}' with {len(final)} questions worth {final.total_points()} points")
student = Student.from_string("Alisher:S-042")
earned = student.take_exam(final, [1, " Def ", 0, "mutable"])
print(f"Alisher earned {earned}/{final.total_points()} points")
print(f"Passed? {student.passed(final.title)}")
try:
q_abstract = Question("Abstract?", 5)
except TypeError:
print("Cannot instantiate abstract class")
Expected Output
Valid ID 'S-001': True
Valid ID '12345': False
Error: Points must be positive
[10pts] What is the capital of France?
[10pts] The keyword to define a function in Python is ___
Final exam: 'Midterm + Pop Quiz' with 4 questions worth 30 points
[ATTEMPT] Answering question...
[ATTEMPT] Correct!
[ATTEMPT] Answering question...
[ATTEMPT] Correct!
[ATTEMPT] Answering question...
[ATTEMPT] Wrong!
[ATTEMPT] Answering question...
[ATTEMPT] Correct!
Alisher earned 25/30 points
Passed? True
Cannot instantiate abstract class