Problem with python class usage

Problem with python class usage
0

#1

I am having a problem with a Python assignment. I have a working program for an auto inventory using a nested dictionary for my inventory.
auto_inv = {0:{‘year’: 0, ‘make’:’’,‘model’:’’,‘color’: ‘’, ‘mileage’: 0}} where the zero key is the inventory number. I can add to it, delete from it, modify inventory, print the inventory or save it to a text file. It works great!
There is a problem though, the assignment wants me to use a class with a main function and an init. I cannot seem to get from the working program into a class Automobile. I have tried different things and get different errors when I try different things. I guess I am saying that I don’t understand the passing of variables to a class, and returning the dictionary key I need to add to my inventory dictionary. Here is the class as I have it written:
class Automobile: # This stuff doesn’t work… and I don’t know how to make it work.
def init(self): #initialize private variables
self._make = " "
self._model = " "
self._color = " "
self._year = 0
self._mileage = 0

def add_auto(self, year, make, model, color, mileage):#collects information about the car
    try:
        self._make = input('Enter make of auto: ')# My code gets to here and spits out a typeError.
        self._model = input('Enter model of auto: ')
        self._year = int(input('Enter year of auto: '))
        self._color = input('Enter color of auto: ')
        self._mileage = int(input('Enter mileage of auto: '))
     
        count = count +1
    
        new_auto= ''
        auto_inv[count] = {}

        auto_inv[count] ['year']= self._year
        auto_inv[count] ['make']= self._make
        auto_inv[count] ['model']= self._model
        auto_inv[count] ['color']= self._color
        auto_inv[count] ['mileage']= self._mileage
        print(' ')
        auto_inv.pop(0, None)
        print(auto_inv)   
        return auto_inv[count]
    
    except ValueError:
        print('You entered an incorrect type, please try again')
        count = count-1 
       
def __str__(self): #Formats the output for printing of inventory
    return(self.year, self.make, self.model, self.color, self.mileage)

Now I am not sure I understand the str thing yet, but I am trying to return these variables in this iteration. I did have it returning a dictionary key, but I couldn’t get as far as entering the model name without an error. I am passing the count as an argument, as I need count to be the key so that it is the correct inventory number.

I’m not even sure the proper way to call the add_auto() method. I have watched videos on lyndadotcom, i’ve re-read the textbook chapter. I just don’t understand it. I feel like some key element to calling a class is not sinking in.

HELP?


#3

Hey there. So without seeing the textbook chapter/assignment I’m not entirely sure what’s going on. Is the add_auto() function part of the Automobile class? or is it an inventory/global function here? On first glance, I think some of the issues here may be due to problems with ‘scope.’ For instance, the variable count is likely a global variable by the looks of it, however in your code it’s going to be initialized as a new variable in the function add_auto() (which I’m assuming is a function in the Automobile class), so every time that function is called it will be reinitialized. On the flip side, if add_auto() is a global function (not a function in the Automobile class) you’re calling variables scoped to an Automobile object as global variables.

Here is how I would personally think about this. Remember, classes are like blueprints for building objects. That means that after building the blueprint you have to instantiate instances of these blueprints as objects before they can be used.
You have a class Automobile with a bunch of attributes specific to a car on initialization. You also have an inventory that houses information derived from each instance of Automobile. Where does a function that adds information to this inventory belong? In the Automobile class, or as part of the inventory makeup? To word this differently: Does a car add itself to an inventory or does someone add the car to an inventory? I tend to think the latter. It doesn’t make much sense for the Automobile class to have a function that adds its info to an inventory, rather the inventory should have a function that adds a car’s info as an entry in itself.

So let’s keep these worlds separate. Build an Automobile blueprint (class) that allows you to create a new car object with the attributes make, model, color, year, & mileage. Then make an inventory blueprint (class) that houses the inventory data, count, as well as has methods for adding a new car, removing a car, modifying an entry, and so forth. Once that’s done you can create an instance of an inventory, and some instances of an automobile – then update the inventory object with the information from the car objects.

class Automobile:
    def __init__(self):
        """ Initialize a new Automobile object """
        self.make = ''
        self.model = ''
        self.color = ''
        self.year = 0
        self.mileage = 0
        self.auto_attributes()  # This function fills in the above attributes

    def auto_attributes(self):
        """ Function to be called upon instantiation to set the automobiles 
           attributes via an input prompt. """
        try:
            self.make = input('Enter make of auto: ')
            self.model = input('Enter model of auto: ')
            self.color = input('Enter color of auto: ')
            self.year = int(input('Enter year of auto: '))
            self.mileage = int(input('Enter mileage of auto: '))
        except ValueError:
            # Probably happens if year/mileage entered are not numeric integers
            print('You entered an incorrect type, please try again')
            # Recursively start the function again, prompting user input
            self.auto_attributes()

    def __str__(self):
        # This string tuple will be returned if an instance of Automobile is
        # being used as a string type argument (such as a print statement)
        return str((self.make, self.model, self.color, self.year, self.mileage))


class Inventory:
    def __init__(self, starting_count=0, starting_inv={}):
        """ Initialize a new Inventory object """
        self.count = starting_count  # count is 0 unless otherwise specificed
        self.inv = starting_inv  # Empty inventory unless otherwise specified

    def __str__(self):
        # Return a string representation of the inventory dictionary
        return str(self.inv)

    def add_auto(self, automobile):
        """ Function to add a new Automobile object's attributes
           to the inventory. """
        if type(automobile) == Automobile:
        # Only add to inventory if automobile is an object of class Automobile
        # This ensures automobile has all the correct attributes and no funny
        # business.
            self.count = self.count + 1
            self.inv[self.count] = vars(automobile)
            # The vars() function returns a dictionary containing all the class
            # attributes. {'make': '...', 'model': '...'', 'color': '...', 
            # 'year': '...', 'mileage': '...'} in this case.
            print(self.inv)
        else:
        # Else clause covers an automobile input other than an Automobile class
        # object by asking if a new Automobile object should be created and
        # added to the inventory.
            print('automobile must be an object of class Automobile')
            new_car_question = input('Would you like to create a new Automobile\
 instance and add it to the inventory?\n(Y)es/(N)o: ')

            if new_car_question[0].lower() == 'y': # If the first letter is Y/y
                new_car = Automobile()
                # Recursively call the function again from the top using the
                # newly created Automobile object as the argument
                self.add_auto(new_car)
            else:
                print('Inventory has not been updated')

    def remove_auto(self, inventory_id):
        """ Function to remove an automobile from the inventory """
        if inventory_id in self.inv.keys():
        # Check if the ID supplied is in the inventory
            print(f"Are you sure you'd like to remove entry {inventory_id},\n\
{self.inv[inventory_id]}\nfrom the inventory?")
            answer = input('(Y)es/(N)o: ')
            if answer[0].lower() == 'y':
                del self.inv[inventory_id]  # Delete the dictionary entry
                print(f'Inventory item with ID {inventory_id} successfully removed.')
            else:
                print('Inventory has not been modified')
        else:
            print('ID supplied was not found in the inventory')

    def modify_auto(self, inventory_id):
        """ Function to modify an existing inventory item """
        if inventory_id in self.inv.keys():
        # Check if the ID supplied is in the inventory
            answer = input(f"Would you like to modify item?\n{self.inv[inventory_id]}\
                             \n(Y)es/(N)o: ")
            if answer[0].lower() =='y':
                try:
                    ref = self.inv[inventory_id].copy()
                    # It's important that ref is a copy, otherwise these
                    # operations would immediately alter the actual inventory
                    # entry
                    ref['make'] = input(f"Make: {ref['make']} --> ")
                    ref['model'] = input(f"Model: {ref['model']} --> ")
                    ref['color'] = input(f"Color: {ref['color']} --> ")
                    ref['year'] = int(input(f"Year: {ref['year']} --> "))
                    ref['mileage'] = int(input(f"Mileage: {ref['mileage']} --> "))
                    self.inv[inventory_id] = ref
                    print(f"Successfully updated ID: {inventory_id} to \n{self.inv[inventory_id]}")
                except ValueError:
                    print('You entered an incorrect type, please try again')
                    self.modify_auto(inventory_id)
            else:
                print('Inventory has not been modified')
        else:
            print('ID supplied was not found in the inventory')

To use these classes we just need to make some objects from them:

# Let's pretend I saved the above file in the working directory
# as auto_classes.py
# I'm using * imports for simplicity but know it's not recommended
# to do this in production
>>>from auto_classes import *

# Create an instance of Inventory for use
>>> inventory = Inventory()
# We'll create a new automobile object too while we're at it
>>> car_one = Automobile()
Enter make of auto: Ford
Enter model of auto: Mustang Shelby GT350
Enter color of auto: Black
Enter year of auto: 2018
Enter mileage of auto: 1200

# test that it worked
>>> vars(car_one)
{'make': 'Ford',
 'model': 'Mustang Shelby GT350',
 'color': 'Black',
 'year': 2018,
 'mileage': 1200}

# Let's check our inventory first
>>> print(inventory)  # should be an empty dictionary
{}

# Let's add car_one to the inventory object
>>> inventory.add_auto(car_one)
{1: {'make': 'Ford', 'model': 'Mustang Shelby GT350', 'color': 'Black', 'year': 2018, 'mileage': 1200}}

# Let's modify it now
>>> inventory.modify_auto(1)  # inventory ID = 1
Would you like to modify item?
{'make': 'Ford', 'model': 'Mustang Shelby GT350', 'color': 'Black', 'year': 2018, 'mileage': 1200}                             
(Y)es/(N)o: yes

Make: Ford --> Toyota
Model: Mustang Shelby GT350 --> Avalon
Color: Black --> White
Year: 2018 --> 2007
Mileage: 1200 --> 175000
Successfully updated ID: 1 to 
{'make': 'Toyota', 'model': 'Avalon', 'color': 'White', 'year': 2007, 'mileage': 175000}
# Check our modified inventory
>>> print(inventory)
{1: {'make': 'Toyota', 'model': 'Avalon', 'color': 'White', 'year': 2007, 'mileage': 175000}}

# Let's add one more car to the inventory
>>>inventory.add_auto('I am feeling lazy')
automobile must be an object of class Automobile

Would you like to create a new Automobile instance and add it to the inventory?
(Y)es/(N)o: yesssssss
Enter make of auto: Honda
Enter model of auto: Accord
Enter color of auto: Silver
Enter year of auto: 2010
Enter mileage of auto: 75000

{1: {'make': 'Toyota', 'model': 'Avalon', 'color': 'White', 'year': 2007, 'mileage': 175000}, 2: {'make': 'Honda', 'model': 'Accord', 'color': 'Silver', 'year': 2010, 'mileage': 75000}}

#Finally, let's remove the Avalon from the inventory object because it broke down
>>> inventory.remove_auto(1)
Are you sure you'd like to remove entry 1,
{'make': 'Toyota', 'model': 'Avalon', 'color': 'White', 'year': 2007, 'mileage': 175000}
from the inventory?

(Y)es/(N)o: Y
Inventory item with ID 1 successfully removed.

I left out a few things for you to figure out like searching the inventory and saving it to a txt file but I hope this helps. Things to keep in mind:

  • Classes are like blueprints for building objects
    • An object needs to be created (instantiated) before class methods/variables can be used
  • Variable scope needs to be kept in mind.
    • Classes and functions each have their own internal scope for variables defined within them.
    • the global keyword can be used to specifically access global variables from within a class or function to avoid a new instance of the variable being created. But try to stay out of the habit of using global variables, it’s often frowned upon.
  • Think critically about what methods belong to which class (object).

I’m sure this is a little different from the book, but I hope this helps you out anyway. Good luck!


#4

Thank you for that informative response!!!
Yeah, it was a function in a class. I got rid of the inputs here, and collected them outside of the class to pass them in. Then when adding them to the dictionary, I had to removed the self from the variables, so _year, _make etc.

The count variable was a global. It was in there from where I took my working add_auto function and pasted it into the class. Just a little clean up and it worked great. Sometimes you just have to sleep on things and then it is a cake walk.
I was just panicing becasue of the looming due date!

How would you handle how a function that is returning multiple variables, returns them in a tuple, but I want to add them as attributes to the dictionary in the above program. For instance:

def auto_attributes_menu():
year = int(input("Enter year of the auto: "))
make = input('Enter make of the auto: ')
model = input('Enter model of the auto: ')
color = input('Enter color of vehicle: ')
mileage = int(input('Enter mileage of vehicle: ‘))
print(’ ')
return year, make, model, color, mileage

This returns a tuple with the information, how would you break it up to pass it to the class as the variables?(For some reason It didn’t save my code formatting up there. Oh Well, the indent is gone but you get the point.)

This is in the main program, gathering variables and passing them to the class Automobile.


#5

Well first I have to ask why you’re creating a function outside the Automobile class to pass in these values? Is the assignment setup that way? It just seems easier to me to give the Automobile class all it needs to set itself up rather than relying on an additional level of complexity – but maybe that’s just me (or I’m misunderstanding the assignment or your intentions).
To me, your configuration simplifies to this: Inventory program asks for automobile attributes --> Inventory program passes those attributes to an Automobile object --> Automobile object passes attributes to Inventory dictionary.
Alternatively: Automobile object asks for attributes --> Automobile object passes attributes to Inventory dictionary.

This goes back to ensuring methods belong to the right objects. If the main program is an inventory, should it really be creating Automobile objects, or just cataloguing them? In my inventory.add_auto('I am feeling lazy') example the inventory object can trigger the creation of a new Automobile object, then add that to its dictionary; however, the responsibility of initializing the automobile’s attributes still relies solely in the Automobile class because inventories don’t make cars, factories do (although making a new factory class just to create the automobile instance seems unreasonable here, so it’s housed in the Automobile class).

Along the same lines of thinking, if the format these attributes is going to take in the inventory is a dictionary, why spit them out as a tuple in the first place?

But to answer your question, if I understand it correctly:

def auto_attributes_menu():
    year = int(input("Enter year of the auto: "))
    make = input('Enter make of the auto: ')
    model = input('Enter model of the auto: ')
    color = input('Enter color of vehicle: ')
    mileage = int(input('Enter mileage of vehicle: ‘))
    return (make, model, color, year, mileage)
class Automobile:
    def __init__(self, make, model, color, year, mileage):
        self._make = make
        self._model = model
        self._color = color
        self._year = year
        self._mileage = mileage
>>> attributes = auto_attributes_menu()
# Fill in all the user prompts
>>> new_car = Automobile(make=attributes[0], model=attributes[1], color=attributes[2],
                         year=attributes[3], mileage=attribute[4])
>>> vars(new_car)
{'_make': '...',
 '_model': '...',
 '_color': '...',
 '_year': '...',
 '_mileage': '...'}

#6

Sweet! That makes perfect sense. Thanks again!