Rafael Knuth wrote: > Hey there, > > I am trying to wrap my head around Class Inheritance in Python, and I > wrote a little program which is supposed to calculate revenues from > customers who don't get a discount (parent class) and those who get a > 30% discount (child class): > > class FullPriceCustomer(object): > def __init__(self, customer, rate, hours): > self.customer = customer > self.rate = rate > self.hours = hours > > print ("Your customer %s made you %s USD this year." % > (customer, rate * hours)) > > class DiscountCustomer(FullPriceCustomer): > discount = 0.7 > def calculate_discount(self, rate, hours): > print ("Your customer %s made you %s USD at a 30% discount > rate this year." % (self.customer, self.rate * rate * discount)) > > customer_one = DiscountCustomer("Customer A", 75, 100) > customer_two = FullPriceCustomer("Customer B", 75, 100) > > The DiscountCustomer class instance gets me the wrong result (it does > not calculate the discount) and I was not able to figure out what I > did wrong here. > Can anyone help? Thanks!
In a real application you would not introduce two classes for this -- a FullPriceCustomer would be a customer with a discount of 0%. Then you mix output with initialisation, so let's fix that first: class FullPriceCustomer(object): def __init__(self, customer, rate, hours): self.customer = customer self.rate = rate self.hours = hours def print_summary(self): print( "Your customer %s made you %s USD this year." % (self.customer, self.rate * self.hours) ) customer = FullPriceCustomer("Customer B", 75, 100) customer.print_summary() Now look at your code: what has to change for a customer who gets a discount? self.rate * self.hours will become self.discount * self.rate * self.hours or as I prefer to store the part that the customer doesn't pay as the discount (1 - self.discount) * self.rate * self.hours To make overriding easy we put that calculation into a separate method: class FullPriceCustomer(object): def __init__(self, customer, rate, hours): self.customer = customer self.rate = rate self.hours = hours def print_summary(self): print( "Your customer %s made you %s USD this year." % (self.customer, self.get_total()) ) def get_total(self): return self.rate * self.hours customer = FullPriceCustomer("Customer B", 75, 100) customer.print_summary() Then the DiscountCustomer class can be written class FullPriceCustomer(object): def __init__(self, customer, rate, hours): self.customer = customer self.rate = rate self.hours = hours def print_summary(self): print( "Your customer %s made you %s USD this year." % (self.customer, self.get_total()) ) def get_total(self): return self.rate * self.hours class DiscountCustomer(FullPriceCustomer): discount = 0.3 def get_total(self): return (1 - self.discount) * self.rate * self.hours customers = [ DiscountCustomer("Customer A", 75, 100), FullPriceCustomer("Customer B", 75, 100), ] for customer in customers: customer.print_summary() If we run that we see that the calculation may be correct, but the discount is not mentioned. We could solve this by overriding print_summary, but here's another way: class FullPriceCustomer(object): summary_template = ( "Your customer {0.customer} made you {0.total} USD this year." ) def __init__(self, customer, rate, hours): self.customer = customer self.rate = rate self.hours = hours def print_summary(self): print(self.summary_template.format(self)) @property def total(self): return self.rate * self.hours class DiscountCustomer(FullPriceCustomer): summary_template = ( "Your customer {0.customer} made you {0.total} USD " "at a {0.discount:.0%} this year." ) discount = 0.3 @property def total(self): return (1 - self.discount) * self.rate * self.hours class VIC(DiscountCustomer): discount = 0.33 customers = [ DiscountCustomer("Customer A", 75, 100), FullPriceCustomer("Customer B", 75, 100), VIC("Customer C", 75, 100), ] for customer in customers: customer.print_summary() If one day you choose to use an isinstance check to see if you have a FullPriceCustomer for customer in customers: if isinstance(customer, FullPriceCustomer): print(customer.customer, "pays the full price") you may be surprised to see that all customers seem to be paying the full price. That's because isinstance(obj, class_) is true even if the actual class of obj is a subclass of class_. Therefore I recommend that you change your class hierarchy to class Customer(object): ... class FullPriceCustomer(Customer): ... class DiscountCustomer(Customer): ... Also, if there are more than two or three fixed discounts you'll put the discount into the instance rather than the class: class DiscountCustomer(Customer): def __init__(self, customer, rate, hours, discount): super().__init__(customer, rate, hours) self.discount = discount _______________________________________________ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor