Search code examples
pythoncalculatorpython-2.5

Python Restaurant Tip Calculator


So I am a complete newbie to coding and need some help. I am trying to create a code for a tip calculator but am not quite sure where I am going with it. I was never in class to be taught by my teacher so I am going out on a whim with this. There is a requirement to have a class but I know I am going about it the wrong way. Any help is appreciated. This is what I have so far:

import decimal

class Tip:

    def __init__(self):
        self.tip=0

    def __str__(self):
        return 'Tip['+str(self.sub_total)+' * '+str(self.percentage)+']'

    def sub_total():
        self.sub_total = input()

    def percentage():
        self.percentage = input()

    def get_tip(percentage, sub_total):
        self.percentage = decimal.Decimal(percentage)
        self.sub_total = decimal.Decimal(sub_total)
        self.tip = ((sub_total * percentage) / 100)
        self.total = (sub_total + tip)
        return self.__str__()

t = Tip()

print ("How much is the bill?")
t.sub_total()

print ("What percentage should the tip be?")
t.percentage()

print (t.get_tip())

Everytime I run it I get this:

get_tip() takes exactly 2 arguments (1 given)

Like I said, I am kindof making it up as I go and any help is appreciated.


Solution

  • Hoo boy, there's a lot of things to cover. I'll try to get it all, but will start with working code.

    class Bill(object):  # note: not Tip!
        def __init__(self, sub_total):
            self.sub_total = sub_total
    
        def calculate_tip(self, tip_pct):
            return self.sub_total * tip_pct
    
        def calculate_total(self, tip_pct):
            return self.sub_total * (1 + tip_pct)
    

    This defines a Bill object. If we want to use it, we could do:

    subtotal = float(raw_input("Bill subtotal: "))
    b = Bill(subtotal)
    

    Then to get the tip we could do

    tip_percentage = float(raw_input("Tip Percent (15% --> 0.15): "))
    tip_amt = b.calculate_tip(tip_percentage)
    

    And to get the grand total:

    grand_total = b.calculate_total(tip_percentage)
    

    Here's where you went wrong:

    Your original class, for reference, looks like:

    class Tip:
    
        def __init__(self,sub_total,percentage):
            self.tip= (sub_total * percentage)
    
        def __str__(self):
            return 'Tip['+str(sub_total)+' * '+str(percentage)+']'
    
        def sub_total():
            sub_total = input()
    
        def percentage():
            percentage = input()
    
        def get_tip(percentage, sub_total):
            percentage = decimal.Decimal(percentage)
            sub_total = decimal.Decimal(sub_total)
            tip = ((sub_total * percentage) / 100)
            total = (sub_total + tip)
            return Tip(total)
    

    We'll talk about it method by method.

    1. __init__

    This one is great! It means you create a Tip object by passing the sub_total and the percentage (as a ratio to 1) in the constructor, so t = Tip(15.00, 0.15) is a 15% tip on $15. Hooray!

    1. __str__

    Only briefly going to go into this one, other than to mention that this looks more like a __repr__ than a __str__ and you should use (/) instead of [/]. Remember that sub_total and percentage aren't attributes of the object -- they're just arguments that were passed to its constructor. You can't reference them here if you don't save them in the __init__ method.

    1. sub_total

    First of all, you can't call this since it doesn't have a self argument. Second of all it doesn't do anything even if you could. sub_total = input() gets user input, then throws it away.

    1. percentage

    See above

    1. get_tip

    You're missing a self argument, again, but I'll talk about it as if it was get_tip(self, percentage, sub_total). You call this by using your already-created Tip object (remember above when we did t = Tip(15, 0.15)?) and calling it with the arguments that you probably passed the constructor. In other words:

    t = Tip(15, 0.15)  # 15% tip on $15
    t.get_tip(0.15, 15)  # ...15% tip on $15, but the grand total...
    

    It doesn't make a whole lot of sense as a function. Especially since half your work is already done for you and saved in self.tip. If this is the pattern you wanted to use for your object, you could do something like:

    class Tip(object):
        def __init__(self, sub_total, tip_pct):
            self.sub_total = sub_total
            self.tip_pct = tip_pct
            # saving these as attributes so we can use them later
            self.grand_total = self.sub_total * (1 + self.tip_pct)
            self.tip_amt = self.sub_total * self.tip_pct
    

    But that really looks more like a series of functions to me, than something that needs to be saved as an object!

    Nota Bene

    Remember that classes are here to provide a state for your code to run in. In this case, the only state you can really apply is "How much to tip," so I suppose you could do the reverse of this and do something like:

    t = Tip(0.15)
    t.calculate_total(amt_of_bill)
    

    But it seems silly to me. You could maybe make a factory function that does something like:

    def tip(tip_pct):
        def wrapped(bill_amt):
            return bill_amt * (1 + tip_pct)
        return wrapped
    

    But that's a little advanced for where you're at.