Week 5 Assignment
Variant 1: Unit Conversion Station
A science lab needs a system that converts measurements using different converters. Some converters follow a strict interface, while others are custom tools built by researchers that happen to work the same way (duck typing).
- Create an abstract base class
Converterthat accepts and storesname. Define an abstract methodconvert(self, value). Add a regular methoddescribe(self, value)that returns the string"<name>: <value> -> <converted>"where<converted>is the output ofself.convert(value). - Create a subclass
CelsiusToFahrenheit(Converter)that sets its name to"CelsiusToFahrenheit". Itsconvertmethod returnsround(value * 9 / 5 + 32, 2). - Create a subclass
KilometersToMiles(Converter)that sets its name to"KilometersToMiles". Itsconvertmethod returnsround(value * 0.621371, 2). - Create a subclass
KilogramsToPounds(Converter)that sets its name to"KilogramsToPounds". Itsconvertmethod returnsround(value * 2.20462, 2). - Create a class
CustomConverter(not inheriting fromConverter) that acceptsnameandfactor. Itsconvert(self, value)returnsround(value * self.factor, 2). Itsdescribe(self, value)returns the same format asConverter.describe. - Create a class
ConversionLogthat stores a list of entries. It has alog(self, converter_name, original, converted)method that appends the string"<converter_name>: <original> -> <converted>"to the list, and ashow(self)method that prints each entry on its own line. - Create a class
ConversionStationthat acceptsnameand holds a list of converters and aConversionLog(composition). It hasadd_converter(self, converter),convert_all(self, value)(prints"=== <name> ===", then for each converter prints itsdescribeoutput and logs the conversion), andshow_log(self)(prints"--- Log for <name> ---"then calls the log’sshowmethod).
Input
station = ConversionStation('Science Lab')
station.add_converter(CelsiusToFahrenheit())
station.add_converter(KilometersToMiles())
station.add_converter(KilogramsToPounds())
station.add_converter(CustomConverter('LitersToGallons', 0.264172))
station.convert_all(100)
print()
station.convert_all(50)
print()
station.show_log()
try:
c = Converter('test')
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
=== Science Lab ===
CelsiusToFahrenheit: 100 -> 212.0
KilometersToMiles: 100 -> 62.14
KilogramsToPounds: 100 -> 220.46
LitersToGallons: 100 -> 26.42
=== Science Lab ===
CelsiusToFahrenheit: 50 -> 122.0
KilometersToMiles: 50 -> 31.07
KilogramsToPounds: 50 -> 110.23
LitersToGallons: 50 -> 13.21
--- Log for Science Lab ---
CelsiusToFahrenheit: 100 -> 212.0
KilometersToMiles: 100 -> 62.14
KilogramsToPounds: 100 -> 220.46
LitersToGallons: 100 -> 26.42
CelsiusToFahrenheit: 50 -> 122.0
KilometersToMiles: 50 -> 31.07
KilogramsToPounds: 50 -> 110.23
LitersToGallons: 50 -> 13.21
Cannot instantiate abstract class
Variant 2: Form Validation Pipeline
A web application needs a flexible form validation system. Some validators follow a strict abstract contract, while one custom validator works through duck typing alone.
- Create an abstract base class
Validatorthat accepts and storesname. Define an abstract methodvalidate(self, value)that should returnTrueorFalse. Add a regular methodcheck(self, value)that callsself.validate(value), determines the status string"PASS"or"FAIL", prints"[<status>] <name>: <value>", and returns the boolean. - Create a subclass
LengthValidator(Validator)that acceptsmin_lenandmax_len, sets its name to"Length(<min_len>-<max_len>)". ItsvalidatereturnsTrueifmin_len <= len(value) <= max_len. - Create a subclass
ContainsDigitValidator(Validator)with name"ContainsDigit". ItsvalidatereturnsTrueif the value contains at least one digit. - Create a subclass
NoSpacesValidator(Validator)with name"NoSpaces". ItsvalidatereturnsTrueif the value contains no spaces. - Create a class
StartsWithUpperValidator(not inheriting fromValidator) withnameset to"StartsWithUpper". It hasvalidate(self, value)that returnsTrueif the value is non-empty and starts with an uppercase letter, andcheck(self, value)that prints the same format asValidator.checkand returns the boolean. - Create a class
ValidationReportthat stores a list of entries. It hasadd(self, validator_name, value, passed)that appends a tuple(validator_name, value, passed), andsummary(self)that counts total, passed, and failed, then prints"Total: <total>, Passed: <passed>, Failed: <failed>". - Create a class
FormFieldthat acceptsfield_nameand holds a list of validators and aValidationReport(composition). It hasadd_validator(self, validator),validate(self, value)(prints'Validating <field_name>: "<value>"', runs each validator’scheckmethod, adds each outcome to the report, and returnsTrueonly if all validators passed), andshow_report(self)(prints"--- Report for <field_name> ---"then calls the report’ssummarymethod).
Input
username_field = FormField('username')
username_field.add_validator(LengthValidator(3, 15))
username_field.add_validator(NoSpacesValidator())
username_field.add_validator(ContainsDigitValidator())
username_field.add_validator(StartsWithUpperValidator())
valid1 = username_field.validate('Admin1')
print(f'Valid: {valid1}')
print()
valid2 = username_field.validate('no')
print(f'Valid: {valid2}')
print()
valid3 = username_field.validate('has space')
print(f'Valid: {valid3}')
print()
username_field.show_report()
try:
v = Validator('test')
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
Validating username: "Admin1"
[PASS] Length(3-15): Admin1
[PASS] NoSpaces: Admin1
[PASS] ContainsDigit: Admin1
[PASS] StartsWithUpper: Admin1
Valid: True
Validating username: "no"
[FAIL] Length(3-15): no
[PASS] NoSpaces: no
[FAIL] ContainsDigit: no
[FAIL] StartsWithUpper: no
Valid: False
Validating username: "has space"
[PASS] Length(3-15): has space
[FAIL] NoSpaces: has space
[FAIL] ContainsDigit: has space
[FAIL] StartsWithUpper: has space
Valid: False
--- Report for username ---
Total: 12, Passed: 6, Failed: 6
Cannot instantiate abstract class
Variant 3: Tax-Aware Shopping Cart
An online store calculates taxes differently depending on the product type. The system uses abstract interfaces to enforce tax and description behavior, inheritance to create product variants, and duck typing for gift cards.
- Create an abstract base class
Taxablewith an abstract methodtax_amount(self). - Create an abstract base class
Describablewith an abstract methoddescribe(self). - Create a class
Product(Taxable, Describable)that acceptsnameandprice. Ifpriceis negative, raise aValueErrorwith the message"Invalid price: <price>". Itstax_amountreturnsround(self.price * 0.10, 2)(10% tax). Itsdescribereturns"<name>: $<price>"with 2 decimal places. - Create a subclass
DiscountedProduct(Product)that additionally acceptsdiscount(a float between 0 and 1). It has afinal_price(self)method that returnsround(self.price * (1 - self.discount), 2). It overridestax_amountto compute 10% offinal_priceinstead. It overridesdescribeto return"<name>: $<price> -> $<final_price> (-<percent>%)"(all prices with 2 decimal places, percent as integer). - Create a subclass
ImportedProduct(Product)that additionally acceptsimport_duty(a float, e.g., 0.15 for 15%). It overridestax_amountto returnround(self.price * 0.10 + self.price * self.import_duty, 2). It overridesdescribeto return"<name>: $<price> (imported, duty <percent>%)"(price with 2 decimal places, percent as integer). - Create a class
GiftCard(not inheriting fromTaxableorDescribable) withnameandprice. Itstax_amountreturns0.0. Itsdescribereturns"<name>: $<price> (gift card, tax-free)"(price with 2 decimal places). - Create a class
Receiptthat stores a list of lines. It hasadd_line(self, description, tax)that appends a tuple(description, tax), andprint_receipt(self)that prints each line as" <description> | tax: $<tax>"(tax with 2 decimal places). - Create a class
ShoppingCartthat acceptscustomer_nameand holds a list of items and aReceipt(composition). It hasadd_item(self, item)andcheckout(self)which prints"Checkout for <customer_name>", iterates over items callingdescribeandtax_amounton each, adds each to the receipt, then prints the receipt, followed by"Subtotal: $<subtotal>","Total Tax: $<total_tax>", and"Grand Total: $<grand_total>"(all with 2 decimal places). The subtotal is the sum of all item prices. The grand total is the subtotal plus total tax.
Input
cart = ShoppingCart('Alisher')
cart.add_item(Product('Laptop', 1000))
cart.add_item(DiscountedProduct('Headphones', 200, 0.25))
cart.add_item(ImportedProduct('Chocolate', 10, 0.15))
cart.add_item(GiftCard('Steam Card', 50))
try:
cart.add_item(Product('Bad Item', -5))
except ValueError as e:
print(f'Skipped: {e}')
cart.checkout()
try:
t = Taxable()
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
Skipped: Invalid price: -5
Checkout for Alisher
Laptop: $1000.00 | tax: $100.00
Headphones: $200.00 -> $150.00 (-25%) | tax: $15.00
Chocolate: $10.00 (imported, duty 15%) | tax: $2.50
Steam Card: $50.00 (gift card, tax-free) | tax: $0.00
Subtotal: $1260.00
Total Tax: $117.50
Grand Total: $1377.50
Cannot instantiate abstract class
Variant 4: Measurement Scaling Workshop
An engineering workshop needs a system that rescales measurements using different scalers. Some scalers follow a strict interface, while others are custom tools built by engineers that happen to work the same way (duck typing).
- Create an abstract base class
Scalerthat accepts and storesname. Define an abstract methodscale(self, value). Add a regular methoddescribe(self, value)that returns the string"<name>: <value> -> <scaled>"where<scaled>is the output ofself.scale(value). - Create a subclass
InchesToCentimeters(Scaler)that sets its name to"InchesToCentimeters". Itsscalemethod returnsround(value * 2.54, 2). - Create a subclass
GallonsToLiters(Scaler)that sets its name to"GallonsToLiters". Itsscalemethod returnsround(value * 3.78541, 2). - Create a subclass
MilesToKilometers(Scaler)that sets its name to"MilesToKilometers". Itsscalemethod returnsround(value * 1.60934, 2). - Create a class
CustomScaler(not inheriting fromScaler) that acceptsnameandfactor. Itsscale(self, value)returnsround(value * self.factor, 2). Itsdescribe(self, value)returns the same format asScaler.describe. - Create a class
ScalingHistorythat stores a list of entries. It has arecord(self, scaler_name, original, scaled)method that appends the string"<scaler_name>: <original> -> <scaled>"to the list, and ashow(self)method that prints each entry on its own line. - Create a class
Workshopthat acceptsnameand holds a list of scalers and aScalingHistory(composition). It hasadd_scaler(self, scaler),scale_all(self, value)(prints"=== <name> ===", then for each scaler prints itsdescribeoutput and records the scaling), andshow_history(self)(prints"--- History for <name> ---"then calls the history’sshowmethod).
Input
workshop = Workshop('Engineering Bay')
workshop.add_scaler(InchesToCentimeters())
workshop.add_scaler(GallonsToLiters())
workshop.add_scaler(MilesToKilometers())
workshop.add_scaler(CustomScaler('OuncesToGrams', 28.3495))
workshop.scale_all(10)
print()
workshop.scale_all(25)
print()
workshop.show_history()
try:
s = Scaler('test')
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
=== Engineering Bay ===
InchesToCentimeters: 10 -> 25.4
GallonsToLiters: 10 -> 37.85
MilesToKilometers: 10 -> 16.09
OuncesToGrams: 10 -> 283.5
=== Engineering Bay ===
InchesToCentimeters: 25 -> 63.5
GallonsToLiters: 25 -> 94.64
MilesToKilometers: 25 -> 40.23
OuncesToGrams: 25 -> 708.74
--- History for Engineering Bay ---
InchesToCentimeters: 10 -> 25.4
GallonsToLiters: 10 -> 37.85
MilesToKilometers: 10 -> 16.09
OuncesToGrams: 10 -> 283.5
InchesToCentimeters: 25 -> 63.5
GallonsToLiters: 25 -> 94.64
MilesToKilometers: 25 -> 40.23
OuncesToGrams: 25 -> 708.74
Cannot instantiate abstract class
Variant 5: Password Strength Checker
A security module needs a flexible password strength checker. Some rules follow a strict abstract contract, while one custom rule works through duck typing alone.
- Create an abstract base class
Rulethat accepts and storesname. Define an abstract methodtest(self, value)that should returnTrueorFalse. Add a regular methodevaluate(self, value)that callsself.test(value), determines the status string"PASS"or"FAIL", prints"[<status>] <name>: <value>", and returns the boolean. - Create a subclass
MinLengthRule(Rule)that acceptsmin_len, sets its name to"MinLength(<min_len>)". ItstestreturnsTrueiflen(value) >= min_len. - Create a subclass
HasUppercaseRule(Rule)with name"HasUppercase". ItstestreturnsTrueif the value contains at least one uppercase letter. - Create a subclass
HasSpecialCharRule(Rule)with name"HasSpecialChar". ItstestreturnsTrueif the value contains at least one non-alphanumeric character. - Create a class
NoRepeatingCharsRule(not inheriting fromRule) withnameset to"NoRepeatingChars". It hastest(self, value)that returnsTrueif no two consecutive characters in the value are the same, andevaluate(self, value)that prints the same format asRule.evaluateand returns the boolean. - Create a class
StrengthReportthat stores a list of entries. It hasadd(self, rule_name, value, passed)that appends a tuple(rule_name, value, passed), andsummary(self)that counts total, passed, and failed, then prints"Total: <total>, Passed: <passed>, Failed: <failed>". - Create a class
PasswordFieldthat acceptsfield_nameand holds a list of rules and aStrengthReport(composition). It hasadd_rule(self, rule),check(self, value)(prints'Checking <field_name>: "<value>"', runs each rule’sevaluatemethod, adds each outcome to the report, and returnsTrueonly if all rules passed), andshow_report(self)(prints"--- Report for <field_name> ---"then calls the report’ssummarymethod).
Input
password_field = PasswordField('password')
password_field.add_rule(MinLengthRule(8))
password_field.add_rule(HasUppercaseRule())
password_field.add_rule(HasSpecialCharRule())
password_field.add_rule(NoRepeatingCharsRule())
valid1 = password_field.check('Str0ng!Pw')
print(f'Valid: {valid1}')
print()
valid2 = password_field.check('short')
print(f'Valid: {valid2}')
print()
valid3 = password_field.check('aabbccdd!!')
print(f'Valid: {valid3}')
print()
password_field.show_report()
try:
r = Rule('test')
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
Checking password: "Str0ng!Pw"
[PASS] MinLength(8): Str0ng!Pw
[PASS] HasUppercase: Str0ng!Pw
[PASS] HasSpecialChar: Str0ng!Pw
[PASS] NoRepeatingChars: Str0ng!Pw
Valid: True
Checking password: "short"
[FAIL] MinLength(8): short
[FAIL] HasUppercase: short
[FAIL] HasSpecialChar: short
[PASS] NoRepeatingChars: short
Valid: False
Checking password: "aabbccdd!!"
[PASS] MinLength(8): aabbccdd!!
[FAIL] HasUppercase: aabbccdd!!
[PASS] HasSpecialChar: aabbccdd!!
[FAIL] NoRepeatingChars: aabbccdd!!
Valid: False
--- Report for password ---
Total: 12, Passed: 7, Failed: 5
Cannot instantiate abstract class
Variant 6: Shipping Cost Calculator
A logistics company calculates shipping costs differently depending on the parcel type. The system uses abstract interfaces to enforce cost and labeling behavior, inheritance to create parcel variants, and duck typing for envelopes.
- Create an abstract base class
Weighablewith an abstract methodshipping_cost(self). - Create an abstract base class
Labelablewith an abstract methodlabel(self). - Create a class
Parcel(Weighable, Labelable)that acceptsnameandweight. Ifweightis negative, raise aValueErrorwith the message"Invalid weight: <weight>". Itsshipping_costreturnsround(self.weight * 0.50, 2)($0.50 per kg). Itslabelreturns"<name>: <weight>kg"with 2 decimal places. - Create a subclass
FragileParcel(Parcel)that additionally acceptssurcharge(a flat fee). It overridesshipping_costto returnround(self.weight * 0.50 + self.surcharge, 2). It overrideslabelto return"<name>: <weight>kg (fragile, +$<surcharge>)"(both with 2 decimal places). - Create a subclass
OversizedParcel(Parcel)that additionally acceptssize_fee(a float, e.g., 0.25 for 25%). It overridesshipping_costto returnround(self.weight * 0.50 + self.weight * self.size_fee, 2). It overrideslabelto return"<name>: <weight>kg (oversized, fee <percent>%)"(weight with 2 decimal places, percent as integer). - Create a class
Envelope(not inheriting fromWeighableorLabelable) withnameandweight. Itsshipping_costreturns1.00(flat rate). Itslabelreturns"<name>: <weight>kg (envelope, flat rate)"(weight with 2 decimal places). - Create a class
ShippingManifestthat stores a list of lines. It hasadd_line(self, description, cost)that appends a tuple(description, cost), andprint_manifest(self)that prints each line as" <description> | cost: $<cost>"(cost with 2 decimal places). - Create a class
ShipmentOrderthat acceptssender_nameand holds a list of parcels and aShippingManifest(composition). It hasadd_parcel(self, parcel)andfinalize(self)which prints"Shipment for <sender_name>", iterates over parcels callinglabelandshipping_coston each, adds each to the manifest, then prints the manifest, followed by"Total Weight: <total_weight>kg"and"Total Cost: $<total_cost>"(both with 2 decimal places). The total weight is the sum of all parcel weights.
Input
order = ShipmentOrder('Sevara')
order.add_parcel(Parcel('Books', 3))
order.add_parcel(FragileParcel('Vase', 2, 5.00))
order.add_parcel(OversizedParcel('Furniture', 20, 0.25))
order.add_parcel(Envelope('Letter', 0.1))
try:
order.add_parcel(Parcel('Bad Parcel', -2))
except ValueError as e:
print(f'Skipped: {e}')
order.finalize()
try:
w = Weighable()
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
Skipped: Invalid weight: -2
Shipment for Sevara
Books: 3.00kg | cost: $1.50
Vase: 2.00kg (fragile, +$5.00) | cost: $6.00
Furniture: 20.00kg (oversized, fee 25%) | cost: $15.00
Letter: 0.10kg (envelope, flat rate) | cost: $1.00
Total Weight: 25.10kg
Total Cost: $23.50
Cannot instantiate abstract class
Variant 7: Currency Exchange Desk
A bank’s currency exchange desk converts amounts using different exchange modules. Some modules follow a strict interface, while others are third-party tools that happen to work the same way (duck typing).
- Create an abstract base class
Exchangerthat accepts and storesname. Define an abstract methodexchange(self, value). Add a regular methoddescribe(self, value)that returns the string"<name>: <value> -> <exchanged>"where<exchanged>is the output ofself.exchange(value). - Create a subclass
USDToEUR(Exchanger)that sets its name to"USDToEUR". Itsexchangemethod returnsround(value * 0.9216, 2). - Create a subclass
USDToGBP(Exchanger)that sets its name to"USDToGBP". Itsexchangemethod returnsround(value * 0.7893, 2). - Create a subclass
USDToJPY(Exchanger)that sets its name to"USDToJPY". Itsexchangemethod returnsround(value * 149.52, 2). - Create a class
CustomExchanger(not inheriting fromExchanger) that acceptsnameandrate. Itsexchange(self, value)returnsround(value * self.rate, 2). Itsdescribe(self, value)returns the same format asExchanger.describe. - Create a class
ExchangeLogthat stores a list of entries. It has arecord(self, exchanger_name, original, exchanged)method that appends the string"<exchanger_name>: <original> -> <exchanged>"to the list, and ashow(self)method that prints each entry on its own line. - Create a class
ExchangeDeskthat acceptsnameand holds a list of exchangers and anExchangeLog(composition). It hasadd_exchanger(self, exchanger),exchange_all(self, value)(prints"=== <name> ===", then for each exchanger prints itsdescribeoutput and records the exchange), andshow_log(self)(prints"--- Log for <name> ---"then calls the log’sshowmethod).
Input
desk = ExchangeDesk('Airport Kiosk')
desk.add_exchanger(USDToEUR())
desk.add_exchanger(USDToGBP())
desk.add_exchanger(USDToJPY())
desk.add_exchanger(CustomExchanger('USDToUZS', 12850.0))
desk.exchange_all(200)
print()
desk.exchange_all(75)
print()
desk.show_log()
try:
e = Exchanger('test')
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
=== Airport Kiosk ===
USDToEUR: 200 -> 184.32
USDToGBP: 200 -> 157.86
USDToJPY: 200 -> 29904.0
USDToUZS: 200 -> 2570000.0
=== Airport Kiosk ===
USDToEUR: 75 -> 69.12
USDToGBP: 75 -> 59.2
USDToJPY: 75 -> 11214.0
USDToUZS: 75 -> 963750.0
--- Log for Airport Kiosk ---
USDToEUR: 200 -> 184.32
USDToGBP: 200 -> 157.86
USDToJPY: 200 -> 29904.0
USDToUZS: 200 -> 2570000.0
USDToEUR: 75 -> 69.12
USDToGBP: 75 -> 59.2
USDToJPY: 75 -> 11214.0
USDToUZS: 75 -> 963750.0
Cannot instantiate abstract class
Variant 8: File Upload Filter
A cloud storage service needs a flexible file upload filter. Some filters follow a strict abstract contract, while one custom filter works through duck typing alone.
- Create an abstract base class
Filterthat accepts and storesname. Define an abstract methodapply(self, value)that should returnTrueorFalse. Add a regular methodcheck(self, value)that callsself.apply(value), determines the status string"PASS"or"FAIL", prints"[<status>] <name>: <value>", and returns the boolean. - Create a subclass
ExtensionFilter(Filter)that accepts a listallowed, sets its name to"Extension(<allowed>)"where<allowed>is the list items joined by","(e.g."Extension(jpg,png,pdf)"). ItsapplyreturnsTrueif the value ends with"."followed by any of the allowed extensions. - Create a subclass
MaxSizeFilter(Filter)that acceptsmax_size(int), name set to"MaxSize(<max_size>)". ItsapplyreturnsTrueiflen(value) <= max_size. - Create a subclass
NoSpacesFilter(Filter)with name"NoSpaces". ItsapplyreturnsTrueif the value contains no spaces. - Create a class
StartsWithLetterFilter(not inheriting fromFilter) withnameset to"StartsWithLetter". It hasapply(self, value)that returnsTrueif the value is non-empty and the first character is a letter, andcheck(self, value)that prints the same format asFilter.checkand returns the boolean. - Create a class
UploadReportthat stores a list of entries. It hasadd(self, filter_name, value, passed)that appends a tuple(filter_name, value, passed), andsummary(self)that counts total, passed, and failed, then prints"Total: <total>, Passed: <passed>, Failed: <failed>". - Create a class
UploadFieldthat acceptsfield_nameand holds a list of filters and anUploadReport(composition). It hasadd_filter(self, filter_obj),validate(self, value)(prints'Validating <field_name>: "<value>"', runs each filter’scheckmethod, adds each outcome to the report, and returnsTrueonly if all filters passed), andshow_report(self)(prints"--- Report for <field_name> ---"then calls the report’ssummarymethod).
Input
upload = UploadField('avatar')
upload.add_filter(ExtensionFilter(['jpg', 'png', 'pdf']))
upload.add_filter(MaxSizeFilter(20))
upload.add_filter(NoSpacesFilter())
upload.add_filter(StartsWithLetterFilter())
valid1 = upload.validate('profile_pic.jpg')
print(f'Valid: {valid1}')
print()
valid2 = upload.validate('my document.exe')
print(f'Valid: {valid2}')
print()
valid3 = upload.validate('123.png')
print(f'Valid: {valid3}')
print()
upload.show_report()
try:
f = Filter('test')
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
Validating avatar: "profile_pic.jpg"
[PASS] Extension(jpg,png,pdf): profile_pic.jpg
[PASS] MaxSize(20): profile_pic.jpg
[PASS] NoSpaces: profile_pic.jpg
[PASS] StartsWithLetter: profile_pic.jpg
Valid: True
Validating avatar: "my document.exe"
[FAIL] Extension(jpg,png,pdf): my document.exe
[PASS] MaxSize(20): my document.exe
[FAIL] NoSpaces: my document.exe
[PASS] StartsWithLetter: my document.exe
Valid: False
Validating avatar: "123.png"
[PASS] Extension(jpg,png,pdf): 123.png
[PASS] MaxSize(20): 123.png
[PASS] NoSpaces: 123.png
[FAIL] StartsWithLetter: 123.png
Valid: False
--- Report for avatar ---
Total: 12, Passed: 9, Failed: 3
Cannot instantiate abstract class
Variant 9: Event Ticket Pricing
A venue calculates ticket prices differently depending on the ticket type. The system uses abstract interfaces to enforce pricing and formatting behavior, inheritance to create ticket variants, and duck typing for complimentary passes.
- Create an abstract base class
Priceablewith an abstract methodservice_fee(self). - Create an abstract base class
Formattablewith an abstract methodformat_ticket(self). - Create a class
Ticket(Priceable, Formattable)that acceptseventandprice. Ifpriceis negative, raise aValueErrorwith the message"Invalid price: <price>". Itsservice_feereturnsround(self.price * 0.12, 2)(12% fee). Itsformat_ticketreturns"<event>: $<price>"with 2 decimal places. - Create a subclass
EarlyBirdTicket(Ticket)that additionally acceptsdiscount(a float between 0 and 1). It has afinal_price(self)method that returnsround(self.price * (1 - self.discount), 2). It overridesservice_feeto compute 12% offinal_priceinstead. It overridesformat_ticketto return"<event>: $<price> -> $<final_price> (-<percent>%)"(all prices with 2 decimal places, percent as integer). - Create a subclass
PremiumTicket(Ticket)that additionally acceptsvip_surcharge(a float, e.g., 0.30 for 30%). It overridesservice_feeto returnround(self.price * 0.12 + self.price * self.vip_surcharge, 2). It overridesformat_ticketto return"<event>: $<price> (premium, surcharge <percent>%)"(price with 2 decimal places, percent as integer). - Create a class
CompPass(not inheriting fromPriceableorFormattable) witheventandprice(always 0). Itsservice_feereturns0.0. Itsformat_ticketreturns"<event>: $0.00 (complimentary)". - Create a class
Invoicethat stores a list of lines. It hasadd_line(self, description, fee)that appends a tuple(description, fee), andprint_invoice(self)that prints each line as" <description> | fee: $<fee>"(fee with 2 decimal places). - Create a class
TicketOrderthat acceptsbuyer_nameand holds a list of tickets and anInvoice(composition). It hasadd_ticket(self, ticket)andfinalize(self)which prints"Order for <buyer_name>", iterates over tickets callingformat_ticketandservice_feeon each, adds each to the invoice, then prints the invoice, followed by"Subtotal: $<subtotal>","Total Fees: $<total_fees>", and"Grand Total: $<grand_total>"(all with 2 decimal places). The subtotal is the sum of all ticket prices. The grand total is the subtotal plus total fees.
Input
order = TicketOrder('Nodira')
order.add_ticket(Ticket('Rock Concert', 80))
order.add_ticket(EarlyBirdTicket('Jazz Night', 120, 0.20))
order.add_ticket(PremiumTicket('Opera Gala', 200, 0.30))
order.add_ticket(CompPass('Staff Meeting', 0))
try:
order.add_ticket(Ticket('Bad Event', -10))
except ValueError as e:
print(f'Skipped: {e}')
order.finalize()
try:
p = Priceable()
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
Skipped: Invalid price: -10
Order for Nodira
Rock Concert: $80.00 | fee: $9.60
Jazz Night: $120.00 -> $96.00 (-20%) | fee: $11.52
Opera Gala: $200.00 (premium, surcharge 30%) | fee: $84.00
Staff Meeting: $0.00 (complimentary) | fee: $0.00
Subtotal: $400.00
Total Fees: $105.12
Grand Total: $505.12
Cannot instantiate abstract class
Variant 10: Nutritional Calculator
A dietitian’s office needs a system that computes nutritional values using different calculators. Some calculators follow a strict interface, while others are third-party plugins that happen to work the same way (duck typing).
- Create an abstract base class
Calculatorthat accepts and storesname. Define an abstract methodcompute(self, value). Add a regular methoddescribe(self, value)that returns the string"<name>: <value> -> <computed>"where<computed>is the output ofself.compute(value). - Create a subclass
CaloriesToKilojoules(Calculator)that sets its name to"CaloriesToKilojoules". Itscomputemethod returnsround(value * 4.184, 2). - Create a subclass
GramsToOunces(Calculator)that sets its name to"GramsToOunces". Itscomputemethod returnsround(value * 0.035274, 2). - Create a subclass
CupsToMilliliters(Calculator)that sets its name to"CupsToMilliliters". Itscomputemethod returnsround(value * 236.588, 2). - Create a class
CustomCalculator(not inheriting fromCalculator) that acceptsnameandfactor. Itscompute(self, value)returnsround(value * self.factor, 2). Itsdescribe(self, value)returns the same format asCalculator.describe. - Create a class
CalculationLogthat stores a list of entries. It has arecord(self, calc_name, original, computed)method that appends the string"<calc_name>: <original> -> <computed>"to the list, and ashow(self)method that prints each entry on its own line. - Create a class
NutritionLabthat acceptsnameand holds a list of calculators and aCalculationLog(composition). It hasadd_calculator(self, calculator),compute_all(self, value)(prints"=== <name> ===", then for each calculator prints itsdescribeoutput and records the computation), andshow_log(self)(prints"--- Log for <name> ---"then calls the log’sshowmethod).
Input
lab = NutritionLab('Diet Clinic')
lab.add_calculator(CaloriesToKilojoules())
lab.add_calculator(GramsToOunces())
lab.add_calculator(CupsToMilliliters())
lab.add_calculator(CustomCalculator('TspsToMl', 4.929))
lab.compute_all(500)
print()
lab.compute_all(120)
print()
lab.show_log()
try:
c = Calculator('test')
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
=== Diet Clinic ===
CaloriesToKilojoules: 500 -> 2092.0
GramsToOunces: 500 -> 17.64
CupsToMilliliters: 500 -> 118294.0
TspsToMl: 500 -> 2464.5
=== Diet Clinic ===
CaloriesToKilojoules: 120 -> 502.08
GramsToOunces: 120 -> 4.23
CupsToMilliliters: 120 -> 28390.56
TspsToMl: 120 -> 591.48
--- Log for Diet Clinic ---
CaloriesToKilojoules: 500 -> 2092.0
GramsToOunces: 500 -> 17.64
CupsToMilliliters: 500 -> 118294.0
TspsToMl: 500 -> 2464.5
CaloriesToKilojoules: 120 -> 502.08
GramsToOunces: 120 -> 4.23
CupsToMilliliters: 120 -> 28390.56
TspsToMl: 120 -> 591.48
Cannot instantiate abstract class
Variant 11: Product Review Moderator
An e-commerce platform needs a flexible review moderation system. Some rules follow a strict abstract contract, while one custom rule works through duck typing alone.
- Create an abstract base class
Criterionthat accepts and storesname. Define an abstract methodjudge(self, value)that should returnTrueorFalse. Add a regular methodevaluate(self, value)that callsself.judge(value), determines the status string"PASS"or"FAIL", prints"[<status>] <name>: <value>", and returns the boolean. - Create a subclass
MinWordsCriterion(Criterion)that acceptsmin_words, sets its name to"MinWords(<min_words>)". ItsjudgereturnsTrueif the number of words in the value (split by spaces) is at leastmin_words. - Create a subclass
MaxLengthCriterion(Criterion)that acceptsmax_len, name set to"MaxLength(<max_len>)". ItsjudgereturnsTrueiflen(value) <= max_len. - Create a subclass
NoBannedWordsCriterion(Criterion)that accepts a listbanned, name set to"NoBannedWords". ItsjudgereturnsTrueif none of the banned words appear in the lowercased value. - Create a class
EndsWithPunctuationCriterion(not inheriting fromCriterion) withnameset to"EndsWithPunctuation". It hasjudge(self, value)that returnsTrueif the value is non-empty and ends with".","!", or"?", andevaluate(self, value)that prints the same format asCriterion.evaluateand returns the boolean. - Create a class
ModerationReportthat stores a list of entries. It hasadd(self, criterion_name, value, passed)that appends a tuple(criterion_name, value, passed), andsummary(self)that counts total, passed, and failed, then prints"Total: <total>, Passed: <passed>, Failed: <failed>". - Create a class
ReviewFieldthat acceptsfield_nameand holds a list of criteria and aModerationReport(composition). It hasadd_criterion(self, criterion),moderate(self, value)(prints'Moderating <field_name>: "<value>"', runs each criterion’sevaluatemethod, adds each outcome to the report, and returnsTrueonly if all criteria passed), andshow_report(self)(prints"--- Report for <field_name> ---"then calls the report’ssummarymethod).
Input
review = ReviewField('comment')
review.add_criterion(MinWordsCriterion(3))
review.add_criterion(MaxLengthCriterion(50))
review.add_criterion(NoBannedWordsCriterion(['spam', 'fake']))
review.add_criterion(EndsWithPunctuationCriterion())
valid1 = review.moderate('Great product overall!')
print(f'Valid: {valid1}')
print()
valid2 = review.moderate('ok')
print(f'Valid: {valid2}')
print()
valid3 = review.moderate('This is spam content')
print(f'Valid: {valid3}')
print()
review.show_report()
try:
c = Criterion('test')
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
Moderating comment: "Great product overall!"
[PASS] MinWords(3): Great product overall!
[PASS] MaxLength(50): Great product overall!
[PASS] NoBannedWords: Great product overall!
[PASS] EndsWithPunctuation: Great product overall!
Valid: True
Moderating comment: "ok"
[FAIL] MinWords(3): ok
[PASS] MaxLength(50): ok
[PASS] NoBannedWords: ok
[FAIL] EndsWithPunctuation: ok
Valid: False
Moderating comment: "This is spam content"
[PASS] MinWords(3): This is spam content
[PASS] MaxLength(50): This is spam content
[FAIL] NoBannedWords: This is spam content
[FAIL] EndsWithPunctuation: This is spam content
Valid: False
--- Report for comment ---
Total: 12, Passed: 8, Failed: 4
Cannot instantiate abstract class
Variant 12: Restaurant Bill Calculator
A restaurant calculates bills differently depending on the order type. The system uses abstract interfaces to enforce cost and display behavior, inheritance to create order variants, and duck typing for loyalty rewards.
- Create an abstract base class
Billablewith an abstract methodtip_amount(self). - Create an abstract base class
Displayablewith an abstract methoddisplay(self). - Create a class
Order(Billable, Displayable)that acceptsdishandprice. Ifpriceis negative, raise aValueErrorwith the message"Invalid price: <price>". Itstip_amountreturnsround(self.price * 0.15, 2)(15% tip). Itsdisplayreturns"<dish>: $<price>"with 2 decimal places. - Create a subclass
HappyHourOrder(Order)that additionally acceptsdiscount(a float between 0 and 1). It has afinal_price(self)method that returnsround(self.price * (1 - self.discount), 2). It overridestip_amountto compute 15% offinal_priceinstead. It overridesdisplayto return"<dish>: $<price> -> $<final_price> (-<percent>%)"(all prices with 2 decimal places, percent as integer). - Create a subclass
DeliveryOrder(Order)that additionally acceptsdelivery_rate(a float, e.g., 0.20 for 20%). It overridestip_amountto returnround(self.price * 0.15 + self.price * self.delivery_rate, 2). It overridesdisplayto return"<dish>: $<price> (delivery, fee <percent>%)"(price with 2 decimal places, percent as integer). - Create a class
LoyaltyReward(not inheriting fromBillableorDisplayable) withdishandprice(always 0). Itstip_amountreturns0.0. Itsdisplayreturns"<dish>: $0.00 (loyalty reward)". - Create a class
BillSummarythat stores a list of lines. It hasadd_line(self, description, tip)that appends a tuple(description, tip), andprint_bill(self)that prints each line as" <description> | tip: $<tip>"(tip with 2 decimal places). - Create a class
TableBillthat acceptsguest_nameand holds a list of orders and aBillSummary(composition). It hasadd_order(self, order)andclose(self)which prints"Bill for <guest_name>", iterates over orders callingdisplayandtip_amounton each, adds each to the bill summary, then prints the bill, followed by"Subtotal: $<subtotal>","Total Tips: $<total_tips>", and"Grand Total: $<grand_total>"(all with 2 decimal places). The subtotal is the sum of all order prices. The grand total is the subtotal plus total tips.
Input
bill = TableBill('Jasur')
bill.add_order(Order('Steak', 45))
bill.add_order(HappyHourOrder('Cocktail', 20, 0.50))
bill.add_order(DeliveryOrder('Sushi Set', 30, 0.20))
bill.add_order(LoyaltyReward('Free Dessert', 0))
try:
bill.add_order(Order('Bad Dish', -15))
except ValueError as e:
print(f'Skipped: {e}')
bill.close()
try:
b = Billable()
except TypeError:
print('Cannot instantiate abstract class')
Expected Output
Skipped: Invalid price: -15
Bill for Jasur
Steak: $45.00 | tip: $6.75
Cocktail: $20.00 -> $10.00 (-50%) | tip: $1.50
Sushi Set: $30.00 (delivery, fee 20%) | tip: $10.50
Free Dessert: $0.00 (loyalty reward) | tip: $0.00
Subtotal: $95.00
Total Tips: $18.75
Grand Total: $113.75
Cannot instantiate abstract class