Week 6 Tutorial: Decorators & Method Types
Problem 1 (Easy): Function Reassignment
You are building a notification system. Different departments need different greeting styles, but they all want to use the same function name internally.
- Create a function
formal_greet(name)that returns the string"Dear {name}, welcome aboard.". - Create a function
casual_greet(name)that returns the string"Hey {name}! What's up?". - Assign
formal_greetto a variable callednotify(without calling it). - Print the result of calling
notify("Aziza"). - Now reassign
notifytocasual_greet. - Print the result of calling
notify("Jasur").
Expected Output
Dear Aziza, welcome aboard.
Hey Jasur! What's up?
Problem 2 (Easy): Simple Decorator
A restaurant wants to print a decorative line before and after announcing each dish.
- Write a decorator function
announce(func)that defines an innerwrapper()function. The wrapper should print"🍽️ NOW SERVING 🍽️", then call the original function, then print"─" * 20. - Apply
@announceto a functiondish()that prints"Palov". - Call
dish().
Expected Output
🍽️ NOW SERVING 🍽️
Palov
────────────────────
Problem 3 (Easy+): Decorator with Arguments
An online store logs every purchase. You need a decorator that works with functions that take arguments.
- Write a decorator
log_purchase(func)that defines awrapper(*args, **kwargs)inside. The wrapper should print"[LOG] Purchase recorded.", call the original function with the same arguments, and return its result. - Create a function
buy(item, price)decorated with@log_purchase. It should return the string"Bought {item} for ${price}". - Call
buy("Laptop", 999)and print the result. - Call
buy("Mouse", 25)and print the result.
Expected Output
[LOG] Purchase recorded.
Bought Laptop for $999
[LOG] Purchase recorded.
Bought Mouse for $25
Problem 4 (Easy+): Static Method
A recipe app needs a helper class that provides unit conversions for cooking measurements.
- Create a class
Kitchenwith no__init__method needed. - Add a static method
cups_to_ml(cups)that returnscups * 236.588rounded to 1 decimal place. - Add a static method
tbsp_to_ml(tbsp)that returnstbsp * 14.787rounded to 1 decimal place. - Call both methods directly through the class (without creating an object) and print the results.
Input
print(Kitchen.cups_to_ml(2))
print(Kitchen.cups_to_ml(0.5))
print(Kitchen.tbsp_to_ml(3))
print(Kitchen.tbsp_to_ml(10))
Expected Output
473.2
118.3
44.4
147.9
Problem 5 (Medium): Class Method as Alternative Constructor
A delivery service tracks packages. Packages are normally created with a name and weight in kilograms, but some warehouses provide data as a single string in the format "name:weight_kg".
- Create a class
Packagewith an__init__(self, name, weight_kg)that stores both attributes. - Add a class method
from_string(cls, data)that takes a string like"Books:3.5", splits it, converts the weight to a float, and returns a newPackageobject. - Add a class method
from_pounds(cls, name, weight_lb)that converts pounds to kilograms (divide by 2.205) and returns a newPackageobject with the weight rounded to 2 decimal places. - Add an instance method
label(self)that returns the string"Package '{name}' — {weight_kg} kg".
Input
p1 = Package("Electronics", 1.2)
p2 = Package.from_string("Books:3.5")
p3 = Package.from_pounds("Clothes", 11)
print(p1.label())
print(p2.label())
print(p3.label())
Expected Output
Package 'Electronics' — 1.2 kg
Package 'Books' — 3.5 kg
Package 'Clothes' — 4.99 kg
Problem 6 (Medium): Repeat Decorator & All Three Method Types
A gym management system tracks members. You need a decorator that repeats any function call a given number of times, and a GymMember class that uses all three method types.
- Write a decorator
repeat(n)that takes an integernand returns a decorator. The inner decorator should make the wrapped function executentimes. Each call should work normally (pass arguments as usual). If the wrapped function returns a value, the wrapper should return the result of the final execution. - Create a class
GymMemberwith:__init__(self, name, membership)that storesnameandmembership(a string like"basic"or"premium").- A class variable
_totalstarting at0, incremented in__init__each time a new member is created. - A static method
is_valid_membership(membership)that returnsTrueif the membership is one of"basic","standard", or"premium", andFalseotherwise. - A class method
get_total(cls)that returns_total. - An instance method
greet(self)decorated with@repeat(2)that prints"Welcome, {name}! ({membership} member)".
Input
print(GymMember.is_valid_membership("premium"))
print(GymMember.is_valid_membership("vip"))
m1 = GymMember("Dilshod", "basic")
m2 = GymMember("Malika", "premium")
m1.greet()
m2.greet()
print(f"Total members: {GymMember.get_total()}")
Expected Output
True
False
Welcome, Dilshod! (basic member)
Welcome, Dilshod! (basic member)
Welcome, Malika! (premium member)
Welcome, Malika! (premium member)
Total members: 2
Problem 7 (Medium+): Student Grade Tracker with Validation & Statistics
A university needs a system to manage student grades. The system should validate inputs, compute statistics, and support creating students from transcript strings.
-
Write a decorator
validate_score(func)that wraps any method receiving ascoreparameter (the second positional argument afterself). If the score is not between 0 and 100 (inclusive), the wrapper should print"Invalid score: {score}"and not call the original method. Otherwise, it should call the method normally and return its result. -
Create a class
GradeTrackerwith:__init__(self, student_name)— stores the student’s name and initializes an empty list_scores.- A class variable
_all_students— an empty list that collects everyGradeTrackerinstance created. - An instance method
add_score(self, score)decorated with@validate_score— appends the score to_scores. - An instance method
average(self)— returns the average of_scoresrounded to 1 decimal place, or0.0if there are no scores. - An instance method
performance_trend(self)— compares each consecutive pair of scores. If more pairs go up than down, returns"Improving". If more go down, returns"Declining". If equal (or fewer than 2 scores), returns"Stable". - A class method
from_transcript(cls, transcript)— takes a string in the format"Name:score1,score2,score3"(e.g.,"Nodira:70,80,92"), creates a newGradeTracker, adds each score, and returns it. - A static method
is_passing(average)— returnsTrueif the average is 60 or above,Falseotherwise. - A class method
class_average(cls)— computes and returns the overall average across all students in_all_students, rounded to 1 decimal place. If no students have scores, returns0.0.
Input
s1 = GradeTracker("Bobur")
s1.add_score(85)
s1.add_score(110)
s1.add_score(72)
s1.add_score(90)
s2 = GradeTracker.from_transcript("Nodira:70,80,92")
print(f"{s1.student_name}: {s1.average()} — {s1.performance_trend()}")
print(f"{s2.student_name}: {s2.average()} — {s2.performance_trend()}")
print(f"Bobur passing: {GradeTracker.is_passing(s1.average())}")
print(f"Nodira passing: {GradeTracker.is_passing(s2.average())}")
print(f"Class average: {GradeTracker.class_average()}")
Expected Output
Invalid score: 110
Bobur: 82.3 — Stable
Nodira: 80.7 — Improving
Bobur passing: True
Nodira passing: True
Class average: 81.5
Problem 8 (Medium+): Auction House with Active Check & Bidding
An online auction house needs a system to manage items and bids. Bidding should only be allowed while an auction is active, and the system should track the most popular item across all auctions.
-
Write a decorator
ensure_active(func)that wraps any method. The wrapper should check ifself._activeisTrue. If it is not, print"Auction closed for {self.name}"and do not call the original method. Otherwise, call the method normally and return its result. -
Create a class
AuctionItemwith:__init__(self, name, starting_price)— stores the item’s name, starting price (float), an empty list_bids, a boolean_activeset toTrue, and appendsselfto the class variable_all_items.- A class variable
_all_items— an empty list that collects everyAuctionIteminstance created. - An instance method
highest_bid(self)— returns the maximum value in_bids, orstarting_priceif there are no bids. - An instance method
place_bid(self, amount)decorated with@ensure_active— ifamountis greater thanhighest_bid(), appendsamountto_bidsand returnsTrue. Otherwise, prints"Bid too low for {name}"and returnsFalse. - An instance method
bid_count(self)— returns the number of bids placed. - An instance method
close_auction(self)— sets_activetoFalse. - A class method
from_catalog(cls, entry)— takes a string in the format"Name-StartingPrice"(e.g.,"Vase-50.0"), parses it, and returns a newAuctionItem. - A static method
is_valid_price(price)— returnsTrueifpriceis greater than 0,Falseotherwise. - A class method
most_popular(cls)— returns the name of the item in_all_itemswith the most bids. If no items have bids, returns"No bids yet".
Input
p1 = AuctionItem("Painting", 100.0)
p2 = AuctionItem.from_catalog("Vase-50.0")
p1.place_bid(150)
p1.place_bid(120)
p1.place_bid(200)
p2.place_bid(75)
p1.close_auction()
p1.place_bid(250)
print(f"{p1.name}: highest = {p1.highest_bid()}, bids = {p1.bid_count()}")
print(f"{p2.name}: highest = {p2.highest_bid()}, bids = {p2.bid_count()}")
print(f"Valid price 50: {AuctionItem.is_valid_price(50)}")
print(f"Valid price -10: {AuctionItem.is_valid_price(-10)}")
print(f"Most popular: {AuctionItem.most_popular()}")
Expected Output
Bid too low for Painting
Auction closed for Painting
Painting: highest = 200, bids = 2
Vase: highest = 75, bids = 1
Valid price 50: True
Valid price -10: False
Most popular: Painting
Problem 9 (Advanced): Parking Garage with Access Control
A smart parking garage restricts entry by vehicle type, tracks capacity, and reports occupancy across multiple locations. The system uses two classes that interact with each other.
-
Write a decorator factory
access_control(allowed_types)that takes a list of allowed vehicle type strings. It returns a decorator whose wrapper checks whether thevehicleargument’svehicle_typeattribute is in the allowed list. If not, prints"Access denied for {vehicle_type} vehicles"and does not call the original method. Otherwise, calls the method normally and returns its result. - Create a class
Vehiclewith:__init__(self, plate, vehicle_type)— stores the plate (string) and vehicle type (e.g.,"car","motorcycle","truck").- A class method
from_entry_log(cls, log)— parses a string in the format"PLATE:TYPE"and returns a newVehicle. - A static method
is_valid_plate(plate)— returnsTrueif the plate is exactly 7 characters long,Falseotherwise.
- Create a class
ParkingGaragewith:__init__(self, name, capacity)— stores the garage name, maximum capacity (int), an empty dictionary_spaces(plate → Vehicle), and appendsselfto the class variable_all_garages.- A class variable
_all_garages— an empty list that collects everyParkingGarageinstance. - An instance method
park(self, vehicle)decorated with@access_control(["car", "motorcycle"])— if the number of vehicles in_spaceshas reachedcapacity, prints"{name} is full"and returnsFalse. Otherwise, adds the vehicle to_spaceskeyed by its plate and returnsTrue. - An instance method
remove(self, plate)— removes the vehicle with the given plate from_spaces. If the plate is not found, prints"Vehicle {plate} not found". - An instance method
available_spaces(self)— returns how many spaces are free. - An instance method
occupancy_rate(self)— returns the percentage of spaces occupied, rounded to 1 decimal place. - A class method
total_parked(cls)— returns the total number of vehicles across all garages in_all_garages.
Input
v1 = Vehicle("AB1234C", "car")
v2 = Vehicle.from_entry_log("XY9876Z:motorcycle")
v3 = Vehicle("TR5555K", "truck")
print(Vehicle.is_valid_plate("AB1234C"))
print(Vehicle.is_valid_plate("AB12"))
g = ParkingGarage("Central", 2)
g.park(v1)
g.park(v3)
g.park(v2)
g.park(Vehicle("CD4444E", "car"))
g.remove("AB1234C")
print(f"Available: {g.available_spaces()}")
print(f"Occupancy: {g.occupancy_rate()}%")
print(f"Total parked: {ParkingGarage.total_parked()}")
Expected Output
True
False
Access denied for truck vehicles
Central is full
Available: 1
Occupancy: 50.0%
Total parked: 1
Problem 10 (Advanced): Online Poll System with Duplicate Prevention
An online voting platform needs a poll system that prevents duplicate votes, validates options, computes vote percentages, and aggregates statistics across all polls.
-
Write a decorator
require_unique(func)that wraps any method whose first argument afterselfisvoter_id. The wrapper should check ifvoter_idis already inself._voters. If it is, print"Voter {voter_id} has already voted"and returnFalsewithout calling the original method. Otherwise, call the method normally and return its result. -
Create a class
Pollwith:__init__(self, question, options)— stores the question (string), a list of option strings, a dictionary_votesthat maps each option to0, an empty set_voters, and appendsselfto the class variable_all_polls.- A class variable
_all_polls— an empty list that collects everyPollinstance. - An instance method
vote(self, voter_id, option)decorated with@require_unique— ifoptionis not in the options list, prints"Invalid option: {option}"and returnsFalse. Otherwise, increments the vote count for that option, addsvoter_idto_voters, and returnsTrue. - An instance method
results(self)— returns a dictionary where each key is an option and the value is that option’s share of total votes as a percentage, rounded to 1 decimal place (e.g., 2 votes out of 3 total →66.7). If no votes have been cast, every option should map to0.0. - An instance method
winner(self)— returns the option with the most votes, or"No votes yet"if no votes have been cast. - A class method
from_config(cls, config)— parses a string in the format"Question|option1,option2,option3"and returns a newPoll. - A static method
is_valid_id(voter_id)— returnsTrueifvoter_idstarts with"V"and the remaining characters are all digits,Falseotherwise. - A class method
total_votes_cast(cls)— returns the total number of votes across all polls in_all_polls.
Input
p1 = Poll("Best language?", ["Python", "Java", "C++"])
p2 = Poll.from_config("Best OS?|Windows,Linux,macOS")
p1.vote("V001", "Python")
p1.vote("V002", "Python")
p1.vote("V003", "Java")
p1.vote("V001", "C++")
p1.vote("V004", "Rust")
p2.vote("V001", "Linux")
p2.vote("V002", "macOS")
print(f"Winner: {p1.winner()}")
print(f"Results: {p1.results()}")
print(f"Valid ID 'V001': {Poll.is_valid_id('V001')}")
print(f"Valid ID 'X99': {Poll.is_valid_id('X99')}")
print(f"Total votes cast: {Poll.total_votes_cast()}")
Expected Output
Voter V001 has already voted
Invalid option: Rust
Winner: Python
Results: {'Python': 66.7, 'Java': 33.3, 'C++': 0.0}
Valid ID 'V001': True
Valid ID 'X99': False
Total votes cast: 5