Search code examples
pythonclassdictionaryraw-input

In python. How do I have a user change a dictionary value, when that dictionary is in a class?


So I had a similar question that was answered in another thread.

How do I update a dictionary value having the user choose the key to update and then the new value, in Python?

Basically, how did one get a nested dictionary value changed via raw_input. I used the solution and it worked well, but I wanted to write the program using classes. So I made a class with a method for editing the dictionary using essentially the same code, however when i try run it in the class method it gives me a "key error" now.

So in the main function this works the solution in the above linked question works great. But in a class method:

class team: # create a class where each team will be an instance
  def __init__(self, name):
    self.name = name #name of team will be passed from main
    self.list_of_players = [] # create a list of the players
    self.position1 = {} # create a dictionary for each of the positions on that team
    self.position2 = {}
    self.roster = [self.position1, self.position2]

  def addplayer(self, player_name): # the name of the player is passed to this method from main
    print 'add stats' # fill out the appropriate stats through raw_input 
    stat1 = raw_input('stat1: ')
    stat2 = raw_input('stat2: ')
    pos = raw_input('POS: ')
    vars()[player_name] = {'stat1' : stat1, 'stat2' : stat2, 'POS' : pos} #create a dictionary 
    # for the player where all his stats are kept
    player = {player_name : vars()[player_name]} # create a dictionary that will show the 
    # player's name as a string and his stats which are held in the dictionary named after him
    self.list_of_players.append(player) # append the new player to the list of players
    if pos == 'p1': # add the player and his stats to the appropriate position on the team
      self.position1[player_name] = player
    elif pos == 'p2':
      self.position2[player_name] = player
    else:
      pass

  def editplayer(self, player_name): # player's name is passed to the edit function from main
    print self.list_of_players # player's name shows up in the list of players for the team
    edit_stat = raw_input('which stat? ') # choose which stat(key) to edit via raw input
    new_value = raw_input('new value: ') # choose the new value to apply to the chosen key
    vars()[player_name][edit_stat] = new_value # here is where it gives a key error! this worked 
 #in fact even trying to call and print the players name gives the key error. 
    #player = vars()[player_name]
    #print player

def main(): # the main function
  loop1 = 0 # creating a loop so one can come back and edit the teams after creating them
  list_of_teams = [] # initializing list of teams
  while loop1 < 1:
    print list_of_teams # show the user what teams are available to choose from
    team_option = raw_input('new team or old: ') # create a new team or work with an old one
    if team_option == 'new':
      team_name = raw_input('team name? ') # get the team name from raw_input
      vars()[team_name] = team(team_name) #create an instance of this team name
      list_of_teams.append(team_name) # add the team to the list
    else:
      team_name = raw_input('which team? ') # choose which existing team to work with
      player_choice = raw_input('new player or old? ') # choose to create or edit existing player
      player_name = raw_input('player_name? ') # choose which player from raw_input
      if player_choice == 'new':
        vars()[team_name].addplayer(player_name) # give player_name to addplayer method 
        print vars()[team_name].list_of_players # shows the new player in the appropriate
        # instance's roster. This method seems to be working fine
      else:
        vars()[team_name].editplayer(player_name) # gives the player's name to the editplayer
        # method for the appropriate instance.  But the player name just raises a key error in
        # edit player method.  I am baffled.
        print vars()[team_name].list_of_players
if __name__ == '__main__':
  main()

When it was all one long function this worked but looked like a disaster. Trying to learn better OOP practices but I can't figure out how to call up that dictionary with by the player's name to change the value. I've spent the past few days reviewing tutorials and questions on classes and dictionaries, but clearly I am misunderstanding something about how variables are passed from function to methods.

The fact that it wont even assign the dictionary vars()[player_name] to a var to be printed out means its not recognizing it as the dictionary that was created in the addplayer methond I think. But the fact that it still lists that dictionary in the list of players means it is existing in that instance. So why isn't it recognizing it when i try to address it in the editplayer method? And how do i call up the embeded dictionary created in one method, to change a value in that dictionary in the second method?

Karl pointed out good points that need clarifying: Here's what the attribues I want are.

self.name- i want an instance for each team created

self.list of players - each team should have its own list of players which are dictionaries holding that persons stats. so team1 should have its own list. team2 a different list etc

self.position1/2 - the players on each team would be filed in their various position dictionaries. so Player joe montana's dictionary of statistics would be found in that team's Quarterbacks dictionary

self.roster - should be that team's roster grouped by positions. So a call to print team1.roster should print those players grouped by positions


Solution

  • 1) vars() is a dictionary of local variables within a function.

    When you are in a method in Python, the contents of the object that you called the method on are not local variables. That's why you have to have a self parameter.

    If you want to look up the players by name, then do that. Don't have a list of players, but instead a dict of players.

    2) vars() is something you should almost never be using. It is used so that you can pretend that a string is a variable name. You do not need to do this for anything that you're doing here. In fact, you do not need a variable at all in most of the places where you're using one. You have more to learn about than just OO here.

    Consider this part for example:

    vars()[team_name] = team(team_name)
    list_of_teams.append(team_name)
    

    Instead of trying to remember the team by name in vars(), again, look up the teams by name. Have a dict of teams instead of a list. To get the names of teams, you can just print the keys of the dictionary.

    Simple is better than complicated. Creating variables on the fly is complicated. Using dictionaries is simple.


    I hate spoon-feeding this much code, but it seems like the only way to get the idea(s - I didn't really say everything above) across this time:

    # Just like we want a class to represent teams, since those are "a thing" in our
    # program, we want one for each player as well.
    
    class player(object):
      __slots__ = ['name', 'stats', 'pos']
      def __init__(self, name, stats, pos):
        self.name = name
        self.stats = stats
        self.pos = pos
    
    
    # Asking the user for information to create an object is not the responsibility of
    # that class. We should use external functions for this.
    def create_player(name):
      print 'add stats' # fill out the appropriate stats through raw_input 
      stat1 = raw_input('stat1: ')
      stat2 = raw_input('stat2: ')
      pos = raw_input('POS: ')
      # Now we create and return the 'player' object.
      return player(name, {'stat1': stat1, 'stat2': stat2}, pos)
    
    
    class team(object):
      __slots__ = ['name_to_player', 'position_to_player']
      def __init__(self):
        # We don't make any lists, just dicts, because we want to use them primarily
        # for lookup. Notice how I've named the attributes. In particular, I **don't**
        # talk about type names. That's just an implementation detail. What we care about
        # is how they work: you put a name in, get a player out.
        self.name_to_player = {}
        self.position_to_player = {}
    
      # Again, we don't ask the questions here; this just actually adds the player.
      def add_player(self, player):
        self.name_to_player[player.name] = player
        self.position_to_player[player.pos] = player
    
      # Again, we don't ask the questions here; this just does the actual edit.
      def edit_player(self, name, stat, new_value):
        self.name_to_player[name].stats[stat] = new_value
    
    
    def main(): # the main function
      teams = {} # dict from team name to team object.
      while True:
        print teams.keys()
        # Your human interface was needlessly awkward here; you know from the supplied name
        # whether it's a new team or an old one, because it will or won't be in your
        # existing set of teams. Similarly for players.
        team_name = raw_input('team name? ')
        if team_name not in teams.keys():
          teams[team_name] = team() # create a new team
        else: # edit an existing one
          team = teams[team_name]
          player_name = raw_input('player name? ')
          if player_name in team.name_to_player.keys(): # edit an existing player
            stat = raw_input("stat? ")
            value = raw_input("value? ")
            team.edit_player(player_name, stat, value)
          else: # add a new player
            team.add_player(create_player(player_name))
    
    if __name__ == '__main__':
      main()
    

    This still isn't doing everything "right", but it should give you more than enough to think about for now.