Trivial problem iterating over values in a dict

In this code piece, self.timers is a dict of the key:value scheme. Keys being strings and values being integers. Each frame, the values are being decremented a little via a task function.

This code works:

for timer in self.ships[id].timers.iterkeys():
	self.ships[id].timers[timer] -= dt
	self.ships[id].timers[timer] = max(0,self.ships[id].timers[timer])

This one doesn’t:

for val in self.ships[id].timers.itervalues():
	val -= dt
	val = max(0,val)

Neither does this:

for timer in self.ships[id].timers.iterkeys():
	val = self.ships[id].timers[timer]
	val -= dt
	val = max(0,val)

And I don’t see why!
Hunting this down has cost me quite some time since it’s fundamental to everything that’s happening in my game. Anybody has an explanation?

This might be your explanation:

Think of iterkeys() and itervalues() as a get function without any set functionality.

In your first snippet you actually get the keys and access the actual values from the dict through those.

In the second snippet you’re only getting and changing the values from the dict, not inside the dict. Here’s a simple analogy to panda’s api:
z = np.getZ()
z = 3

As you might guess, this isn’t the same as np.setZ(3) and in fact doesn’t change the actual z value of the np. You’re only getting a local copy of the number. For immutable objects (e.g. strings, integers) this means you cannot change the object itself. Any assignment to the variable will only overwrite it.

The same applies to your last snippet.
What you need to add at the end is:
self.ships[id].timers[timer] = val

Here’s a small snippet that should elaborate on the whole problem:

>>> d = {1:1, 2:2}
>>> item1 = d[1]
>>> id(item1)
>>> id(d[1])
>>> item1 = 444
>>> id(item1)

As you can see after assignment the old object behind item1 (integer with value 1) is gone and a new one (an integer with the value 444) is stored instead. Not a value of the object was changed, but the whole object. That said you can’t change a dict using a value you got from that dict. Only way is to directly assign a value to the dict like so:

>>> d[1] = 444
>>> # or
>>> d[1] = item1

Alright, thank you for your answers. I think I heard of the copy thing before but it didn’t cross me as the cause thinking about the problem.

This one works now:

for timer, val in self.ships[id].timers.iteritems():
			val -= dt
			self.ships[id].timers[timer] = max(0,val)

However I am a little disappointed since itervalues() makes way for iteritems(), which might be a bit heavier on performance. Not that it would matter in my case … just as an observation.