python-3.xfunctiondictionaryconditional-statementsunbound

How to solve Unbound Local error in my conditional block


I need to find the discount of an order I used the following code to make the string input of order numbers to a dictionary and thenused sum() to find the total of the order However I wish to have an offer discount if there is one-1 and one-3 and (one-4 or one-5 or one-6) But then after the conditional block when I want to multiply it I receive an Unbound Error

def compute_cost(order):
"""
    Function 2: compute_cost(order)
    Parameters: order (String)
    Return: Final cost of order
"""
numcount = {}
orderlist = map(int, order)
for i in orderlist:
    if numcount.get(i):
        numcount[i] += 1
    else:
        numcount[i] = 1
for i in numcount:
    if i == 1:
        numcount[i] = numcount[i]*4.25
    elif i == 2:
        numcount[i] = numcount[i]*2.50
    elif i == 3:
        numcount[i] = numcount[i]*2.00
    elif i == 4:
        numcount[i] = numcount[i]*1.25
    elif i == 5:
        numcount[i] = numcount[i]*1.50
    elif i == 6:
        numcount[i] = numcount[i]*1.75
    elif i == 7:
        numcount[i] = numcount[i]*3.75
    else:
        return print("Your order has a number outside of the range (1:7)")
    order_total = sum(numcount.values())
    if(numcount[1] == 1 and
       numcount[3] == 1 and
       (numcount[4] == 1 or
       numcount[5] == 1 or
       numcount[6] == 1)):
        discount1 = 0.20
    order_total1 = order_total*discount1
return order_total1

Please help me Thank you for your time and effort

EDIT If you have a better way for me to find the values and save them in a dictionary I am open to constructive criticism too


Solution

  • Depending on the input, the numcount-dict may or may not have all the keys.

    Case 1: UnboundLocalError

    When calling the function with compute_cost('12323123'), the numcount-dict becomes:

    {1: 2, 2: 3, 3: 3}
    

    The if-statement first checks if numcount[1] == 1, which evaluates to False. Therefore the whole expression is False and Python doesn't even (need to) check the rest. (This is called short-circuit evaluation.)

    Because the if-statement evaluates to False, discount1 is not set at all, so you get UnboundLocalError: local variable 'discount1' referenced before assignment.

    Solution: Add an else-clause that sets discount1 to 1 (= no discount) when the condition is False:

    if (numcount[1] == 1 and numcount[3] == 1 and (numcount[4] == 1 or
        numcount[5] == 1 or numcount[6] == 1)):
        discount1 = 0.20
    else:
        discount1 = 1
    

    Case 2: KeyError

    Now, when calling the function with compute_cost('32235664'), the numcount-dict becomes:

    {3: 2, 2: 2, 5: 1, 6: 2, 4: 1}
    

    The if-statement first checks if numcount[1] == 1, but that key does not exist so Python raises a KeyError. Depending on your input and how far Python needs to evaluate the if-statement, you may get that KeyError or not.

    Solution: Make sure that the numcount-dict contains all keys from the beginning. You already know how many items the dict must have because you limit your input to the range (1:7). Therefore you can initalize the dict as:

    numcount = {1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0}
    

    EDIT: is there a better way?

    Sure there is, there almost always is a better way:

    prices = {'1':4.25, '2':2.50, '3':2.00, '4':1.25, '5':1.50, '6':1.75, '7':3.75}
    orders = '12323456'
    
    total = sum(prices[order] for order in orders)
    if (all(orders.count(type) >= 1 for type in '13') and           # multiple ANDs with 'all'
        any(True for type in '456' if orders.count(type) >=1)):     # multiple ORs with 'any'
        discount = 0.2
    else:
        discount = 1
    print('Order value: {}\nDiscount: {}\nOffer: {:.2f}'.format(total, discount, discount * total))
    

    You now can easily extend your prices dictionary or the conditions for a discount. I assumed that the condition for a discount is that at least one item was ordered, not exactly one. Therefore I used >=1 which you can change into ==1 if it needs to be exactly one.