0

I'm writing a piece of code in Python to create an order system for a PC building shop, and have an 'order' class, which contains a method to print the order. When I reference this (create an order) in one of my functions (to place the order), it works fine. As in:

order1 = order(1,'Luke','p5','16gb','1tb','19in','midi','4usb')

But when I reference it inside my 'main' function, it gives this error when I run the code:

  File "program.py", line 103, in main
    order1 = order(1,'Luke','p5','16gb','1tb','19in','midi','4usb')
UnboundLocalError: local variable 'order' referenced before assignment

Which makes no sense to me, as it works perfectly well in my other function. The entire code is printed below. You can see in the 'main' function the line where the error is called first on the first and second line of the function, and later when if the text 'test' is entered, it will also return an error, for no apparent reason. I am using Python 3.6.3, on Mac OS X.

#!/usr/bin/env python3

class order(object):
    def __init__(self, orderNo, customer, processor, ram, storage, screen, case, usb):
        self.orderNo = orderNo
        self.customer = customer
        self.processor = processor
        self.ram = ram
        self.storage = storage
        self.screen = screen
        self.case = case
        self.usb = usb

    def stockCost(self):
        global componentCosts
        return componentCosts[self.processor] + componentCosts[self.ram] + componentCosts[self.storage] + componentCosts[self.screen]+ componentCosts[self.case]+ componentCosts[self.usb]

    def salePrice(self):
        return self.stockCost() * 1.2

    def print_order(self):
        """Prints the data of an order in a viewable way for the vendor"""
        return 'Order number: {}, for customer: {}.\n'.format(self.orderNo, self.customer) \
        + '{} processor.\n'.format(self.processor) \
        + '{} of ram.\n'.format(self.ram.upper()) \
        + '{} of storage.\n'.format(self.storage.upper()) \
        + '{} screen.\n'.format(self.screen) \
        + '{}{} case.\n'.format(self.case.lower()[0].upper(), self.case.lower()[1:])\
        + '{} {} ports.\n'.format(self.usb[0], self.usb[1:].upper()) \
        + 'Your chosen build will cost ${:0.2f}.\n'.format(self.salePrice())

    def user_print_order(self):
        """Prints the data of an order in a viewable way for the customer"""
        return '{}, your order number is {}.\n'.format(self.customer, self.orderNo) \
        + 'You have chosen a {} processor.\n'.format(self.processor) \
        + 'You have chosen {} of ram.\n'.format(self.ram.upper()) \
        + 'You have chosen to have {} of storage.\n'.format(self.storage.upper()) \
        + 'You have chosen a {} screen.\n'.format(self.screen) \
        + 'You have chosen a {}{} case.\n'.format(self.case.lower()[0].upper(), self.case.lower()[1:])\
        + 'You have chosen to have {} {} ports.\n'.format(self.usb[0], self.usb[1:].upper()) \
        + 'Your chosen build will cost ${:0.2f}.\n'.format(self.salePrice())


def place_order():
    global componentList
    global stock
    global orders
    orderOK = True
    componentChoices = {}
    print ('Please choose your components, These are your options:\n(please enter the component as they appear on the component list)\n' + componentList)
    name = input('Please enter your name: ')
    componentChoices['processor'] = input('Please enter what processor you would like: ')
    componentChoices['ram'] = input('Please enter how much ram you would like: ')
    componentChoices['storage'] = input('Please enter how much storage you would like: ')
    componentChoices['screen'] = input('Please enter how big a screen you would like: ')
    componentChoices['case'] = input('Please enter what size case you would like: ')
    componentChoices['usb'] = input('Please enter whether you would like 4 USB ports or 2 (enter 4USB or 2USB): ')
    for part in componentChoices:
        choice = componentChoices[part].lower()
        if stock[choice] < 1:
            print ('Your choice of {} is out of stock, please choose a different option.\n'.format(part) + componentList)
            componentChoice[part] = input('Enter your new choice of {}.'.format(part))
            if stock[componentChoices[part]] < 1:
                print ('Your choice of {} is also out of stock, your order has thus failed to go through. Please try again later.')
                orderOK = False
                return None
        else: stock[componentChoices[part].lower()] -= 1

    toBeReturned = order(len(orders)+1, name, componentChoices['processor'], componentChoices['ram'], componentChoices['storage'], componentChoices['screen'], componentChoices['case'], componentChoices['usb'])
    print (toBeReturned.user_print_order())
    query = input('Is this information correct? Enter "Y" or "N".')
    while True:
        if query.lower() == 'y':
            break
        if query.lower == 'n':
            print('Your order has not been placed, please re-place the order')
            return None

    return toBeReturned


componentList = 'Processor: p3, p5 or p7\nRAM: 16GB or 32GB\nStorage: 1TB or 2TB\nScreen: 19in or 23in\nCase Size: Mini or Midi\nNo of Ports: 2USB or 4USB'

componentCosts = {
'p3': 100, 'p5': 120, 'p7': 200, '16gb': 75, '32gb': 150,
'1tb': 50, '2tb': 100, '19in': 65, '23in': 120, 'mini': 40,
'midi': 70, '2usb': 10, '4usb': 20
}
stock = {
'p3': 100, 'p5': 100, 'p7': 100, '16gb': 100, '32gb': 100,
'1tb': 100, '2tb': 100, '19in': 100, '23in': 100, 'mini': 100,
'midi': 100, '2usb': 100, '4usb': 100
}

orders = {}

#~ order1 = order(1,'Luke','p5','16gb','1tb','19in','midi','4usb')
#~ print (order1.print_order())

def main():
    order1 = order(1,'Luke','p5','16gb','1tb','19in','midi','4usb')
    print (order1.print_order())
    global componentList
    global stock
    global orders
    while True:
        print ('\nEnter "make" to place an order, "view" to view previous orders, "stock" to view the stock levels, and "quit" to quit the program. Quitting the program will lose records of previous orders, please back them up by copying the output from entering "view".')
        command = input().lower()
        while True:
            if command == 'quit':
                return False
            if command == 'view':
                for order in orders:
                    print (orders[order].print_order()+'\n')
                break
            if command == 'make':
                #~ globals()["order" + str(len(orders)+1)] = place_order()
                order1 = order()
                if globals()["order" + str(len(orders)+1)] == None:
                    break
                else:
                    orders[len(orders)+1] = globals()["order" + str(len(orders)+1)]
                    break
            if command == 'stock':
                for i in stock:
                    print(i + ': ' + str(stock[i]))
            if command == 'test':
                for i in range(10):
                    globals()["order" + str(i)] = order(i,'Luke','p5','16gb','1tb','19in','midi','4usb')
                    orders[len(orders)+1] = globals()["order" + str(i)]
            else:
                print('Invalid option entered please try again')
                break

if __name__ == "__main__":
    main()
martineau
  • 119,623
  • 25
  • 170
  • 301
Luke Bligh
  • 27
  • 3
  • 1
    You're redefining `order` later in your main function, here: `for order in orders`. Follow proper naming guidelines and rename your class to `Order`, then you won't have this problem. – Aran-Fey Jan 26 '18 at 22:18
  • Thank you very much @Rawing, I wasn't aware of that naming protocol, this being my first project making use of classes etc. – Luke Bligh Jan 26 '18 at 22:22
  • @Rawing: that dup you cited seems to hinge on assigning to a global variable that isn't declared as such; the problem above is simply redefining the name. I think we need a different dup target. – Prune Jan 26 '18 at 22:25
  • `for order in orders` looks like a bad idea... – goodvibration Jan 26 '18 at 22:25
  • I would not have answered had I seen the duplicate, just my observed issue I had assumed was related to the class itself, and had not noticed the error after perusing the code for a while, and having searched for a related query not found anything about classes. – Luke Bligh Jan 26 '18 at 22:30
  • @Prune Sorry, I don't see the difference. In both cases there's 1) a global variable 2) a global variable being accessed in a function and 3) an assignment to that global variable inside that function. The only difference I can make out is that the assignment is a `for` loop in this case. But if you think you have a more suitable dupe, go ahead and add it. – Aran-Fey Jan 26 '18 at 22:31
  • I was looking; Martijn hammered it closed before I got there. Since OP sees the solution you intended, I deem my quibble to be non-functional; I'll save it for compiler construction class. :-) – Prune Jan 26 '18 at 22:37

1 Answers1

1

In your main function, you do a loop:

for order in orders:
    ...

This is a hidden assignment to order, causing the compiler to mark order as a local variable, hence the UnboundLocalError: local variable 'order' referenced before assignment

Edit:

As noted in a comment by @Rawing this would have been avoided if you followed PEP8 naming conventions. class names should be CapitalCase.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172