[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