[Tutor] Class Inheritance
Peter Otten
__peter__ at web.de
Tue Feb 21 06:01:40 EST 2017
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
More information about the Tutor
mailing list