-4

I'm trying to use a for loop to iterate through a set of objects, check if those objects store string data (specifically their 'Pokemon.evolution' variable) which is the same as a the name of one of the objects in the set, and then replace that string with the object of the matching name.

I've tried various ways of writing the for loop, but nothing seems to do what I want it to. Here it is in "plain speak" to hopefully explain what I mean:

for x in ():
    if Pokemon.name is in pokemon_db:
        pokemon_db = Pokemon.evolution
    else:
        pass

As recommended, I should show some of my attempted for loops, which are here:

def evolution_needs_updating_1():
    for Pokemon in pokemon_db:
        if Pokemon.evolution in pokemon_db is type(str):
            print("Pokemon evolution needs updating")
        print("Correct evoltuion in place")

''' Error Message:
File "Pokedex_Project\pokedex_project.(v.0.1.2).py", line 102, in <module>
    evolution_needs_updating_1()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "Pokedex_Project\pokedex_project.(v.0.1.2).py", line 98, in evolution_needs_updating_1
    if Pokemon.evolution in pokemon_db is type(str):
       ^^^^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'evolution' '''

def evolution_needs_updating_2():
    for Pokemon in pokemon_db:
        if Pokemon_db.evolution is str:
            print("Pokemon evolution needs updating")

''' Error Message:
File "Pokedex_Project\pokedex_project.(v.0.1.2).py", line 109, in <module>
    evolution_needs_updating_2()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "Pokedex_Project\pokedex_project.(v.0.1.2).py", line 106, in evolution_needs_updating_2
    if Pokemon_db.evolution is str:
       ^^^^^^^^^^
NameError: name 'Pokemon_db' is not defined. Did you mean: 'pokemon_db'? '''

for pokemon in pokemon_db:
    if pokemon.evolution == str in pokemon_db:
        print(Pokemon.HasEvolution)
''' Error Message:
pokedex_project.(v.0.1.2).py", line 112, in <module>
    if pokemon.evolution == str in pokemon_db:
       ^^^^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'evolution' '''


The for loop I'm trying to make is to automate something I can do manually, and here is the code for doing it manually:

print(type(ivysaur.evolution)) # Output: <class 'str'> - Shows it's still the orignal string I inputted
print(ivysaur.evolution) # Output: venusaur - The original string

# Evolution updates to object's attributes
ivysaur.evolution = venusaur # Changes the ivysaur .evolution object's attribute to the venusaur
charmeleon.evolution = charizard # Same idea for Charmeleon
wartortle.evolution = blastoise # And same again for Wartortle

print(ivysaur.pokedex_number) # Output: 0002
print(ivysaur.evolution.name) # Output: Venusaur - Now showing self.name for Venusaur, meaning the change worked
print(ivysaur.evolution.pokedex_number) # Output: 0003 - Showing Venusaur's self.pokedex_number, again, showing it changed correctly
print(f"Ivysaur's evolution is {ivysaur.evolution.name}, and it originally evolves from {ivysaur.pre_evolution.name}.") # Output: Ivysaur's evolution is Venusaur, and it originally evolves from Bulbasaur. 

Just for clarification, here is the class & the dict storing all of the Pokemon Class variables together:

class Pokemon:
    def __init__(self, name, pokedex_number, height, weight, category, abilities, type, weaknesses, pre_evolution, evolution, image):
        self.name = name
        self.pokedex_number = pokedex_number
        self.height = height # in Metres (Format: float)
        self.weight = weight # in Kilograms (Format: float)
        self.category = category
        self.abilities = abilities
        self.type = type
        self.weaknesses = weaknesses
        self.pre_evolution = pre_evolution # If n/a - Mark as ' False '
        self.evolution = evolution # If n/a - Mark as ' False '
        self.image = image # Pokedex image stored in 'pokedex_images' folder
        ...

bulbasaur = Pokemon("Bulbasaur", "0001", 0.7, 6.9, "Seed", ["Overgrow"], ["Grass", "Poison"], ["Fire", "Ice", "Flying", "Psychic"], None, "ivysaur", ...)
ivysaur = Pokemon("Ivysaur", "0002", 1.0, 13.0, "Seed", ["Overgrow"], ["Grass", "Poison"], ["Fire", "Ice", "Flying", "Psychic"], bulbasaur, "venusaur", ...)
venusaur = Pokemon("Venusaur", "0003", 2.0, 100.0, "Seed", ["Overgrow"], ["Grass", "Poison"], ["Fire", "Ice", "Flying", "Psychic"], ivysaur, None, ...)
charmander = Pokemon("Charmander", "0004", 0.6, 8.5, "Lizard", ["Blaze"], ["Fire"], ["Water", "Ground", "Rock"], None, "charmeleon", ...)
charmeleon = Pokemon("Charmeleon", "0005", 1.1, 19.0, "Flame", ["Blaze"], ["Fire"], ["Water", "Ground", "Rock"], charmander, "charizard", ...)
charizard = Pokemon("Charizard", "0006", 1.7, 90.5, "Flame", ["Blaze"], ["Fire"], ["Water", "Ground", "Rock"], "charmeleon", None, ...)
squirtle = Pokemon("Squirtle", "0007", 0.5, 9.0, "Tiny Turtle", "Torrent", ["Water"], ["Grass", "Electric"], None, "wartortle", ...)
wartortle = Pokemon("Wartortle", "0008", 1.7, 90.5, "Turtle", "Torrent", ["Water"], ["Grass", "Electric"], squirtle, "blastoise", ...)
blastoise = Pokemon("Blastoise", "0009", 1.7, 90.5, "Shellfish", "Torrent", ["Water"], ["Grass", "Electric"], "wartortle", None, ...)

pokemon_db = {
    "0001" : bulbasaur,
    "0002" : ivysaur,
    "0003" : venusaur,
    "0004" : charmander,
    "0005" : charmeleon,
    "0006" : charizard,
    "0007" : squirtle,
    "0008" : wartortle,
    "0009" : blastoise}
2

4 Answers 4

4

It seems you want 2 things, a method which decided what the evolution should become, and a loop which updates it for all pokemon in your list.

One depends on pokemon_db and the other loops over it, but that doesn't really matter:

pokemon_db = {
    "0001" : bulbasaur,
    "0002" : ivysaur,
    "0003" : venusaur,
    "0004" : charmander,
    "0005" : charmeleon,
    "0006" : charizard,
    "0007" : squirtle,
    "0008" : wartortle,
    "0009" : blastoise}

def updated_evolution(current_evolution):
    for key, pokemon in pokemon_db.items():
        if pokemon.name == current_evolution:
            return pokemon
    return current_evolution
    

for key, pokemon in pokemon_db.items():
    pokemon.evolution = updated_evolution(pokemon.evolution)

This one time operation will loop over all the pokemon and update each evolution.

The method will make the decision what to update to based on the current value and the names of the pokemon.

Sign up to request clarification or add additional context in comments.

4 Comments

Hi Neil, thanks for your attempt to fix the issue. I'm now recieving an error message: "pokemon.evolution = updated_evolution(pokemon.evolution) ^^^^^^^^^^^^^^^^^ AttributeError: 'str' object has no attribute 'evolution'" I'm still very new to all of this so I know I'm likely not going about this right and probably causing more headaches than I should.
@mycode.exe It seems that you've changed the question since I answered it, and now pokemon_db is a dictionary instead of just a set. I've adjusted the for loop in the answer appropriately.
Hi Neil, thank you for updating your answer. I changed it to a dict since I saw in some posts that helps for iteration compared to other storage for the data. I'm still getting this error however: " Pokedex_Project\pokedex_project.(v.0.1.3).py", line 126, in updated_evolution if pokemon.name == current_evolution: ^^^^^^^^^^^^ AttributeError: 'str' object has no attribute 'name' It seems to be the recurring issue that trying to call the '.name' part of the class during the for loop causes the issues. As that is why most of my loops failed.
@mycode.exe sorry, try again now (I'd changed one of the 2 loops to work with a dictionary, but not the second)
2

You could use a class dictionary mapping populated as instances are created and then use properties to update the string attributes to instances as they are accessed.

Store the original string or instance in an internal attribute and use the property to update it to an instance if it exists; otherwise, report an error (maybe the string is misspelled, or the referenced Pokemon doesn't exist yet). Once the database is fully populated, the evolution and pre_evolution attributes will update to the instances when accessed. A for loop won't be needed.

class Pokemon:

    db = {}  # dictionary to map names to instances

    def __init__(self, name, pre_evolution, evolution):
        self.name = name
        self._pre_evolution = pre_evolution  # string or instance as implementation detail
        self._evolution = evolution  # string or instance as implementation detail
        Pokemon.db[self.name] = self  # map name to instance in class database

    # Debug representation when object is printed.
    def __repr__(self):
        return f'Pokemon({self.name!r})'

    # Helper function to lookup instance from string
    @classmethod
    def _update(cls, attribute):
        if isinstance(attribute, str):  # if attribute is a string...
            if attribute in cls.db:  # and string is a key in the database...
                return cls.db[attribute]  # return the instance value
            else:
                raise KeyError(f'{attribute!r} instance does not exist')
        return attribute  # wasn't a string, return unchanged

    @property
    def evolution(self):
        self._evolution = self._update(self._evolution)
        return self._evolution

    @property
    def pre_evolution(self):
        self._pre_evolution = self._update(self._pre_evolution)
        return self._pre_evolution

bulbasaur = Pokemon('Bulbasaur', None, 'Ivysaur')
ivysaur = Pokemon('Ivysaur', bulbasaur, 'Venusaur')
venusaur = Pokemon('Venusaur', ivysaur, None)

for pm in Pokemon.db.values():  # iterate through objects in database
    print(f'{pm!r}', end='')
    if pm.pre_evolution is not None:
        print(f' comes from {pm.pre_evolution!r}', end='')
    if pm.evolution is not None:
        print(f' evolves to {pm.evolution!r}', end='')
    print()

Output:

Pokemon('Bulbasaur') evolves to Pokemon('Ivysaur')
Pokemon('Ivysaur') comes from Pokemon('Bulbasaur') evolves to Pokemon('Venusaur')
Pokemon('Venusaur') comes from Pokemon('Ivysaur')

Comments

0

Perhaps this method will be useful to you (it is a modification of yours).
First, let's create a small dictionary to obtain Pokemon objects based on their names.

pokes = {
    "bulbasaur" : bulbasaur,
    "ivysaur" : ivysaur,
    "venusaur" : venusaur,
    "charmander" : charmander,
    "charmeleon" : charmeleon,
    "charizard" : charizard,
    "squirtle" : squirtle,
    "wartortle" : wartortle,
    "blastoise" : blastoise
}

The main problem is that you are trying to obtain objects from a dictionary without using their keys.

And now, the method that checks whether there is a need to evolve and, if so, does so.

def evolution_needs_updating():

      # we obtain the "keys" of "pokemon_db"
    keys = pokemon_db.keys()
 
    for key in keys:

          # obtain the "Pokemon"
        poke = pokemon_db.get( key )

          # only if "poke.evolution" is a string
        if type( poke.evolution ) == str:
            print( f"Pokemon {poke.name} evolution needs updating")
               # we apply evolution
            poke.evolution = pokes.get( poke.evolution )
            print( f"  evolution -> {poke.evolution.name}")
        else:
            print( f"Pokemon {poke.name} correct evolution in place")
        print()

Here is the complete code:

class Pokemon:
    def __init__(self, name, pokedex_number, height, weight, category, abilities, type, weaknesses, pre_evolution, evolution, image):
        self.name = name
        self.pokedex_number = pokedex_number
        self.height = height # in Metres (Format: float)
        self.weight = weight # in Kilograms (Format: float)
        self.category = category
        self.abilities = abilities
        self.type = type
        self.weaknesses = weaknesses
        self.pre_evolution = pre_evolution # If n/a - Mark as ' False '
        self.evolution = evolution # If n/a - Mark as ' False '
        self.image = image # Pokedex image stored in 'pokedex_images' folder
        ...

bulbasaur = Pokemon("Bulbasaur", "0001", 0.7, 6.9, "Seed", ["Overgrow"], ["Grass", "Poison"], ["Fire", "Ice", "Flying", "Psychic"], None, "ivysaur", ...)
ivysaur = Pokemon("Ivysaur", "0002", 1.0, 13.0, "Seed", ["Overgrow"], ["Grass", "Poison"], ["Fire", "Ice", "Flying", "Psychic"], bulbasaur, "venusaur", ...)
venusaur = Pokemon("Venusaur", "0003", 2.0, 100.0, "Seed", ["Overgrow"], ["Grass", "Poison"], ["Fire", "Ice", "Flying", "Psychic"], ivysaur, None, ...)
charmander = Pokemon("Charmander", "0004", 0.6, 8.5, "Lizard", ["Blaze"], ["Fire"], ["Water", "Ground", "Rock"], None, "charmeleon", ...)
charmeleon = Pokemon("Charmeleon", "0005", 1.1, 19.0, "Flame", ["Blaze"], ["Fire"], ["Water", "Ground", "Rock"], charmander, "charizard", ...)
charizard = Pokemon("Charizard", "0006", 1.7, 90.5, "Flame", ["Blaze"], ["Fire"], ["Water", "Ground", "Rock"], "charmeleon", None, ...)
squirtle = Pokemon("Squirtle", "0007", 0.5, 9.0, "Tiny Turtle", "Torrent", ["Water"], ["Grass", "Electric"], None, "wartortle", ...)
wartortle = Pokemon("Wartortle", "0008", 1.7, 90.5, "Turtle", "Torrent", ["Water"], ["Grass", "Electric"], squirtle, "blastoise", ...)
blastoise = Pokemon("Blastoise", "0009", 1.7, 90.5, "Shellfish", "Torrent", ["Water"], ["Grass", "Electric"], "wartortle", None, ...)

pokemon_db = {
    "0001" : bulbasaur,
    "0002" : ivysaur,
    "0003" : venusaur,
    "0004" : charmander,
    "0005" : charmeleon,
    "0006" : charizard,
    "0007" : squirtle,
    "0008" : wartortle,
    "0009" : blastoise
}
    
pokes = {
    "bulbasaur" : bulbasaur,
    "ivysaur" : ivysaur,
    "venusaur" : venusaur,
    "charmander" : charmander,
    "charmeleon" : charmeleon,
    "charizard" : charizard,
    "squirtle" : squirtle,
    "wartortle" : wartortle,
    "blastoise" : blastoise
}

def evolution_needs_updating():

      # we obtain the "keys" of "pokemon_db"
    keys = pokemon_db.keys()
 
    for key in keys:

          # obtain the "Pokemon"
        poke = pokemon_db.get( key )

          # only if "poke.evolution" is a string
        if type( poke.evolution ) == str:
            print( f"Pokemon {poke.name} evolution needs updating")
               # we apply evolution
            poke.evolution = pokes.get( poke.evolution )
            print( f"  evolution -> {poke.evolution.name}")
        else:
            print( f"Pokemon {poke.name} correct evolution in place")
        print()

evolution_needs_updating()

The approach you proposed (and which I followed) has the problem of complicating a second evolution, so I decided to make a more profound change. What do you think?.

First, we create a list of lists with the data from the different objects (we remove pre_evolution and evolution).

data = [
    ["Bulbasaur", "0001", 0.7, 6.9, "Seed", ["Overgrow"], ["Grass", "Poison"], ["Fire", "Ice", "Flying", "Psychic"], ...],
    ["Ivysaur", "0002", 1.0, 13.0, "Seed", ["Overgrow"], ["Grass", "Poison"], ["Fire", "Ice", "Flying", "Psychic"], ...],
    ["Venusaur", "0003", 2.0, 100.0, "Seed", ["Overgrow"], ["Grass", "Poison"], ["Fire", "Ice", "Flying", "Psychic"], ...],
    ["Charmander", "0004", 0.6, 8.5, "Lizard", ["Blaze"], ["Fire"], ["Water", "Ground", "Rock"], ...],
    ["Charmeleon", "0005", 1.1, 19.0, "Flame", ["Blaze"], ["Fire"], ["Water", "Ground", "Rock"], ...],
    ["Charizard", "0006", 1.7, 90.5, "Flame", ["Blaze"], ["Fire"], ["Water", "Ground", "Rock"], ...],
    ["Squirtle", "0007", 0.5, 9.0, "Tiny Turtle", "Torrent", ["Water"], ["Grass", "Electric"], ...],
    ["Wartortle", "0008", 1.7, 90.5, "Turtle", "Torrent", ["Water"], ["Grass", "Electric"], ...],
    ["Blastoise", "0009", 1.7, 90.5, "Shellfish", "Torrent", ["Water"], ["Grass", "Electric"], ...]
]

A few small modifications to construct the object differently...

class Pokemon:
    def __init__( self, name ):
        self.name = name
    
    def set( self, data ):
        self.pokedex_number = data[ 0 ]
        self.height =  data[ 1 ] # in Metres (Format: float)
        self.weight =  data[ 2 ] # in Kilograms (Format: float)
        self.category =  data[ 3 ]
        self.abilities =  data[ 4 ]
        self.type =  data[ 5 ]
        self.weaknesses =  data[ 6 ]
        # self.pre_evolution = pre_evolution # If n/a - Mark as ' False '
        self.image =  data[ 7 ] # Pokedex image stored in 'pokedex_images' folder
        
    def setEvolution( self, evolution ):
        self.evolution = evolution # If n/a - Mark as ' False '

We create a dictionary that allows us to access objects by name...

pokes = {}

for lis in data:
    name = lis[ 0 ]
    aux = Pokemon( name )
    aux.set( lis[ 1:] )
    aux.setEvolution( aux )
    pokes[ name.lower() ] = aux

This other dictionary allows us to obtain, if available, the evolution of a given object...

evolutions = { 
    "bulbasaur" : pokes.get( "ivysaur" ),
    "ivysaur" : pokes.get( "venusaur" ),
    "venusaur" : None,
    "charmander" :  pokes.get( "charmeleon" ),
    "charmeleon" : pokes.get( "charizard" ),
    "charizard" : None,
    "squirtle" : pokes.get( "wartortle" ),
    "wartortle" : pokes.get( "blastoise" ),
    "blastoise" : None
}

def evolution_needs_updating():

  # we obtain the "keys" of "pokemon_db"
keys = pokes.keys()

for key in keys:

      # obtain the "Pokemon"
    poke = pokes.get( key )

      # we obtain the evolution or “None” if it does not exist
    evolution = evolutions.get( poke.evolution.name.lower() )
    
    if evolution != None:
        print( evolution.name )
        print( f"Pokemon {poke.name} evolution needs updating")
          # we apply evolution
        poke.evolution = evolution
        print( f"  evolution -> {poke.evolution.name}")
    else:
        print( f"Pokemon {poke.name} correct evolution in place")
    #print()
evolution_needs_updating()
print( "-----")
evolution_needs_updating()
print( "-----")
evolution_needs_updating()

3 Comments

Hi Marce, thank you for your attempt to fix the issue. I've ran the function by just putting 'evolution_needs_updating()' as the last line of code of my project, and it's throwing up this error message: line 152, in evolution_needs_updating print( f" evolution -> {poke.evolution.name}") ^^^^^^^^^^^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'name'
I have edited my response to show the complete code. If you copy it as is, you will see that it works.
I have added a new implementation that may improve what we have.
0

I've managed to figure out how to answer my own query. Having everyones input was incredibly helpful in teaching me about certain aspects of Python and how classes interact with dictionaries. All of your answers helped massively to guide me to my solution, so I am very grateful for the contributions from: "Neil Butcher", "Mark Tolonen" and "Marce Puente".

Here is how I managed to get the code to use the dictionary's values for each pokemon and also the data stored within the variables/instances of the class to find and then replace Pokemon's evolutions if they need updating.

Firstly, I needed to change the dictionary from having the keys for each variable as their pokedex number and just use their name as the key, to help the for loop I use later on with its comparisons.

pokemon_database = {
    "bulbasaur" : bulbasaur,
    "ivysaur" : ivysaur,
    "venusaur" : venusaur,
    "charmander" : charmander,
    "charmeleon" : charmeleon,
    "charizard" : charizard,
    "squirtle" : squirtle,
    "wartortle" : wartortle,
    "blastoise" : blastoise,
    "caterpie" : caterpie,
    "metapod" : metapod,
    "butterfree" : butterfree,
    "weedle" : weedle,
    "kakuna" : kakuna,
    "beedrill" : beedrill
    }

Then, after much trial and error with different versions of the for loop, I stumbled into creating this loop. As far as I can tell, it loops through the dictionary using the values of the stored items, rather than the keys, and compares these variables' stored "evolution" data if it is a string or not. If it is a string, then it replaces it with the corresponding variable name for the evolution it has found.

def update_evolutions(pokemon_database):
    for pkmon in pokemon_database.values():
        if pkmon.evolution:
            pkmon.evolution = pokemon_database[pkmon.evolution]
            updated_pokemon_list.append(pkmon.evolution.name) 

update_evolutions(pokemon_database)

Although not part of the actual loop, the "updated_pokemon_list" line is to add the Pokemon that have been found that needed updating, and then adding them to a list so I could check which Pokemon it had found during its loop that needed updating, just to check what it's doing.

I then added some code before and after the loop to make sure it was doing what I intended it to do, and I'll include that in its entirety for transparency.

# Debugging test - See which Pokemon were updateds
updated_pokemon_list: list = []

print(f"Bulbasaur evolves into {bulbasaur.evolution}, then it evolves eventually into {ivysaur.evolution}.") # Wouldn't let me add a further ".name" after each evolution since it caused an error. Showing it was still stored as a string.

# Automatically update any Pokemon's evolutions to link to the correct variable
def update_evolutions(pokemon_database):
    for pkmon in pokemon_database.values():
        if pkmon.evolution:
            pkmon.evolution = pokemon_database[pkmon.evolution]
            updated_pokemon_list.append(pkmon.evolution.name) 

update_evolutions(pokemon_database)


print(bulbasaur.evolution.name)
print(f"Bulbasaur evolves into {bulbasaur.evolution.name}, then it evolves eventually into {ivysaur.evolution.name}.")

print(updated_pokemon_list)

The output of this block was:

Bulbasaur evolves into ivysaur, then it evolves eventually into venusaur.

Ivysaur

Bulbasaur evolves into Ivysaur, then it evolves eventually into Venusaur.

['Ivysaur', 'Venusaur', 'Charmeleon', 'Charizard', 'Wartortle', 'Blastoise', 'Metapod', 'Butterfree', 'Kakuna', 'Beedrill']

Again, thank you for all your help.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.