1

Apologies in advance, I loathe to ask such a newb question (this is the first language I'm learning) but there's clearly a gap in my understanding, and I can't find a link to any content which solves the issue I'm experiencing.

The below code defines 3 dictionaries, and an empty list. I then run a for loop to populate the list with 30 "Slow Aliens".

I then run another loop on the first 10 entries in the list in order to update that particular instance to a "Fast Alien" dictionary. However, I seem to be replacing every dictionary in the list.

Any help pointing me to some articles which discuss/solve/educate me further would be appreciatively received.

slow_alien = {
    "Name": "Slow Alien",
    "Colour": "Green",
    "Points": 10,
    "X Position": 15,
    "Y Position": 20,
    "Current Speed": "Slow",
    "Height": 1.8,
}
medium_alien = {
    "Name": "Medium Alien",
    "Colour": "Yellow",
    "Points": 15,
    "X Position": 30,
    "Y Position": 20,
    "Current Speed": "Medium",
    "Height": 2.2,
}
fast_alien = {
    "Name": "Fast Alien",
    "Colour": "Red",
    "Points": 20,
    "X Position": 45,
    "Y Position": 20,
    "Current Speed": "Fast",
    "Height": 2.6,
}
aliens = []

for alien in range(30):
    aliens.append(slow_alien)

for alien in aliens[:10]:
    if alien == slow_alien:
        alien.update(fast_alien)

for alien in aliens:
    for key, value in alien.items():
        print(str(key) + ": " + str(value))
    print("")
print("The aliens list contains " + str(len(aliens)) + " dictionaries")

Thanks,

Carl.

The Rezzer
  • 13
  • 2
  • Also see https://stackoverflow.com/questions/240178/list-of-lists-changes-reflected-across-sublists-unexpectedly It's not quite the same problem, but the end result is the same: a list containing multiple references to the same object. – PM 2Ring Dec 07 '17 at 12:57
  • Who says that they need 30 *copies* of the alien dictionaries? What if they really just want 10 references to a single "constant" `fast_alien` and 20 references to a single "constant" `slow_alien`? IMO the problem is that they mistake the `dict.update` method for replacing an entry in a list. The correct way to replace an item in a list would be to reassign it using `aliens[i] = fast_alien` (and probably using `enumerate` to get the index). – mkrieger1 Dec 07 '17 at 13:08
  • In that case the question would be a duplicate of https://stackoverflow.com/questions/2582138/finding-and-replacing-elements-in-a-list-python. – mkrieger1 Dec 07 '17 at 13:10
  • @PM2Ring, thanks, answer checked. – The Rezzer Dec 07 '17 at 13:20
  • @mkrieger1, that's great info to know and assist me also, thank you. – The Rezzer Dec 07 '17 at 13:20

3 Answers3

1

You said:

I then run a for loop to populate the list with 30 "Slow Aliens".

But that's wrong, you actually populate the list with 1 slow alien thirty times.

If you actually wanted 30 of them you would have to make a copy when adding them to the list.

for alien in range(30):
    aliens.append(slow_alien.copy())
Duncan
  • 92,073
  • 11
  • 122
  • 156
1

The list has 30 references to the same slow_alien dictionary.

To fix this, try something like this:

aliens = []

for _ in range(30):
    aliens.append(slow_alien.copy())

for alien in aliens[:10]:
    alien.update(fast_alien)

which follows your current approach, though you could also skip the update and just create the fast ones immediately:

aliens = []

for _ in range(10):
    aliens.append(fast_alien.copy())
for _ in range(20):
    aliens.append(slow_alien.copy())
lxop
  • 7,596
  • 3
  • 27
  • 42
  • 2
    As a general rule: Python never copies anything unless you explicitely ask for it. A good read about what python's variables really are can be found here: https://nedbatchelder.com/text/names1.html – bruno desthuilliers Dec 07 '17 at 12:48
  • 1
    Ah! Thank you! The fact that it's referencing the dictionary and not copying it is what I was missing! I'll do some reading around the subject. Thanks again! :) – The Rezzer Dec 07 '17 at 12:50
  • @bruno - Nice, thank you. – The Rezzer Dec 07 '17 at 12:51
  • 1
    That article by Ned is Required Reading for all Python programmers, IMHO. It's a shame that Ned doesn't post here much these days. – PM 2Ring Dec 07 '17 at 12:56
  • @brunodesthuilliers that really is a great video; it also pointed me to the open.edx.org site where there are some great free courses. Thanks again! – The Rezzer Dec 10 '17 at 14:40
1

Creating a new copy of the dictionary is the correct answer, as has already been pointed out. I want to make more explicit what actually goes wrong with your code.

for alien in range(30):
    aliens.append(slow_alien)

slow_alien refers to the same dict, so you get 30 references of that (same) object.

for alien in aliens[:10]:
    if alien == slow_alien:
        alien.update(fast_alien)

The update() call is what messes everything up. The first alien in the loop is a reference to the slow_alien dict. So what you really do is slow_alien.update() and you effectively change the object itself. It will successfully enter the conditional block for the remaining iterations because each alien and slow_alien refer to the same object, namely fast_alien. You can see this if you print slow_alien == fast_alien at the end of your code.

If each item is a different copy, then you update that copy itself without touching the original slow_alien. It can also be written more compactly with a list comprehension.

aliens = [slow_alien.copy() for _ in range(30)]
Reti43
  • 9,656
  • 3
  • 28
  • 44