0

I've looked at questions like Creating instances in a loop but I need to create new instances with different values each time, not a bunch of clones of the instance.

Below, I have written a restaurant tip calculator (with tax) for instances of the class Bill. I wrote a class method that creates a new instance when the calculating for the first instance is complete.

While this works if I have the .py file open to keep adding new instances and calling all the class methods needed, it would be far more useful if I somehow created a loop that would create a new instance when I type "yes" in the self.choice of the second class method.

My previous attempts to make a loop resulted in creating unnamed instances. "Return Bill(example, example, example)" doesn't do it for me because I would not be able to call methods on that (I probably could but it would give headaches and would not be pythonic.) Nor would I be able to append that to a list.

persons = []

class Bill:
    def __init__(self, check, tax, tip):
        self.check = check
        self.tax = tax
        self.tip = tip

    def addPersons(self):
        self.choice = input("Do you want to calculate for another person?")
        if self.choice == "Yes" or self.choice == "yes":
            person2 = Bill(float(input("Check: ")), float(input("Tax: ")), float(input("Tip: ")))
            return person2
        else:
            pass

    def percent(self):
        self.tax = self.tax/100
        self.tip = self.tip/100

    def calculate(self):
        self.result_1 = self.check + (self.check * self.tax)
        self.result_final = self.result_1 + (self.result_1 * self.tip)
        self.difference = self.result_final - self.result_1
        self.advice = self.result_1, "is your check with tax added.", self.difference, "is how much tip you need to pay.", self.result_final, "is your total."
        return self.advice

a = Bill(float(input("Check: ")), float(input("Tax: ")), float(input("Tip: ")))
a.percent()
a.calculate()
print(a.advice)
persons.append(a)

b = a.addPersons()
b.percent()
b.calculate()
print(b.advice)
persons.append(b)

c = b.addPersons()
c.percent()
c.calculate()
print(c.advice)
persons.append(c)

Thank you for your time and help. :)

Community
  • 1
  • 1
Byte
  • 509
  • 6
  • 15
  • I'm not clear on what you're trying to accomplish that would rule out loops. Can you be more specific on what you want to do and what your attempts with a loop did? – TigerhawkT3 Jul 14 '15 at 04:05
  • Sure. (I'm not ruling out loops, I want a loop. I was just trying to explain how my own loops failed.) Since I need a dynamic amount of instances (going out with different amounts of friends), I thought a "while self.choice == 'yes'" loop would create a new instance every time the previous instance was done calculating. I'm just not knowledgeable enough to make a working loop. – Byte Jul 14 '15 at 04:16

1 Answers1

3

I would refactor the addPersons() method out of the class and do things like what's shown below. Note I also made calculate() automatically call percent() so that doesn't have to be done externally.

This is a better design because it moves responsibility for interacting with the user and getting input outside of the class itself (which aren't really its concern). It also allows it to be used with different user interfaces or programmatically, say from data in a database or other container.

class Bill:
    def __init__(self, check, tax, tip):
        self.check = check
        self.tax = tax
        self.tip = tip

    def percent(self):
        self.tax = self.tax/100
        self.tip = self.tip/100

    def calculate(self):
        self.percent()
        self.result_1 = self.check + (self.check * self.tax)
        self.result_final = self.result_1 + (self.result_1 * self.tip)
        self.difference = self.result_final - self.result_1
        self.advice = (self.result_1, "is your check with tax added.",
                       self.difference, "is how much tip you need to pay.",
                       self.result_final, "is your total.")
        return self.advice

bills = []
while True:
    choice = input("Do you want to calculate for another person?")
    if choice.lower().startswith("y"):
        break
    bill = Bill(float(input("Check: ")), float(input("Tax: ")),
                float(input("Tip: ")))
    bill.calculate()
    print(*bill.advice)
    bills.append(bill)

The loop does not create named instances of the Bill class. Instead it stores them all in a list called bills. If you wanted to associate a person's name to each one, you could instead put them in a dictionary that was keyed by name.

bills = {}
while True:
    choice = input("Do you want to calculate for another person?")
    if choice.lower().startswith("y"):
        break
    person_name = input("Enter the name of the person: ")
    if not person_name:
        continue  # ask about continuing again
    bill = Bill(float(input("Check: ")), float(input("Tax: ")),
                float(input("Tip: ")))
    bill.calculate()
    print("{}'s bill:".format(person_name))
    print(*bill.advice)
    bills[person_name] = bill
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Thank you for optimizing my code and providing this answer. One question however, how would I rename each instance so that they are not all "bill"? I think that was the biggest thing frustrating me when I was thinking of a way to make a loop. Also, is there a good way to use __str__ when printing the bills list? (sorry for so many edits) – Byte Jul 14 '15 at 04:27
  • 1
    It's possible, but you wouldn't want to programmatically name the instances because the rest of your code would have no way of knowing what variable names were created. It's better to put them in a container like a `list` and reference them as `bill[0]`, `bill[1]`, etc. You could add a `name` attribute to instances of the `Bill` class that had a person's name in it. Then you could `print(bill[i].name)` when needed. Alternatively you could also store instances in a dictionary that was keyed by people's names. I'm not sure what you meant about using **str** when printing the bill list. – martineau Jul 14 '15 at 04:45
  • Please consider accepting and/or up-voting my answer if has helpful. Thanks. – martineau Jul 14 '15 at 04:48
  • 2
    I'd recommend using `choice.lower().startswith('y')` instead of `choice.lower()[0]` to handle the case where the user just presses Enter without any characters. I'd also unpack the output to `print(*bill.advice)` so that it prints the `tuple` more tidily. And it could be good to clarify that a reference like `bills[0]` is just as valid as one like `a`. – TigerhawkT3 Jul 14 '15 at 04:55
  • @TigerhawkT3: Good suggestions. Thanks. – martineau Jul 14 '15 at 05:21
  • @martineau Would you mind briefly explaining the significance of the "continue" in a while loop such as this? If not, it's alright I'll just do some more reading on it. – Byte Jul 14 '15 at 05:38
  • Unlike `break`, which stops the loop, `continue` just skips the rest of the statements in it and goes back to top (where it retests the loop expression to see if it's true or not). – martineau Jul 14 '15 at 05:50