Pergunta

I'm expecting my frustration to be overridden with some enlightenment - here's a minimal version of the script to demonstrate the problem:

First I create a dictionary:

dic  = {
    'foo':{}, 
    'bar':{}
    }

Then we instantiate a template dictionary that can be iteratively appended to keys of dic:

appendic= {  
    'is':'',       #  '' is a terminal value to be replaced later
}

So here we append appendic to each of the keys in dic:

dic['foo'] = appendic
dic['bar'] = appendic

Now we replace the terminal values, '', with something meaningful:

dic['foo']['is'] = 'foo'
dic['bar']['is'] = 'bar' 

At this point, my intuition tells me that if we call:

print(dic['foo']['is']) we get 'foo'

But instead Python returns 'bar' ... to my un-trained mind that is counter-intuitive.

Questions:

  • How can I tell Python to keep the keys of dic independent?
  • Why is this the default behaviour? What use cases does this have?
Foi útil?

Solução

When you assign a appendic to two different keys, Python doesn't make a copy. It assigns a reference instead.

As a result, both dic['please_make_me_Foo'] and dic['dont_make_him_Bar'] refer to the same object. These are not separate dictionaries, they are both the same object, the one appendic also references to.

If you expected these to be separate dictionaries, create a copy of appendic instead. The dict.copy() method creates a shallow copy of a dictionary:

dic['please_make_me_Foo']= appendic.copy()
dic['dont_make_him_Bar'] = appendic.copy()

Shallow means that a new dictionary is created and all references to keys and values contained are copied over.

If appendic itself contains values that are also dictionaries, these would not be copied. The new copy and appendic would both refer to the same values. In most cases, that's not a problem because most primitive values (strings, integers, etc.) are immutable, and you never notice references are shared as you replace such values with new ones.

Outras dicas

You make a dict:

appendic= {  
    'Python_made_me':''
}

Add it to your other dict twice

dic['please_make_me_Foo']= appendic
dic['dont_make_him_Bar'] = appendic

And set the single dict's Python_made_me value twice

dic['please_make_me_Foo']['Python_made_me'] = 'Foo'
dic['dont_make_him_Bar']['Python_made_me']  = 'Bar' 

But because they're the same dict, the second line overwrites the first

If you need to copy it, you need to use the copy method:

dic['please_make_me_Foo']= appendic.copy()
dic['dont_make_him_Bar'] = appendic.copy()

ok, I'm just going to write this as a complement to the other answers. When you manipulate a dictionary, you manipulate the reference to an instance, which is the root cause of your mistake. Using hex(id(foo)) you get the memory address of foo, so let's show the address of d instance in the following example to make that tangible:

>>> hex(id(d))
'0x10bd95e60'
>>> hex(id(e[1]))
'0x10bd95e60'
>>> hex(id(f[1]))
'0x10bd95e60'

so if you add or remove values from e[1], you're actually changing the same instance as the one pointed by d, and as a dictionary is mutable, i.e. you can change values within.

Now you're wondering why that does not happen when you're handling integers? Because, in fact it does, it's just that integers are not mutable:

>>> i = 1
>>> hex(id(i))
'0x10ba51e90'
>>> j = i
>>> hex(id(j))
'0x10ba51e90'
>>> i = 2
>>> hex(id(i))
'0x10ba51eb0'

i.e. i is pointing to another place in the memory.

It's possible to create a mutable integer though, by using a class:

>>> class Integer:
...   def __init__(self, i):
...     self.i = i
... 
>>> i = Integer(2)
>>> hex(id(i))
'0x10bd9b410'
>>> j = i
>>> hex(id(j))
'0x10bd9b410'
>>> j.i = 2
>>> i.i
2
>>> hex(id(i))
'0x10bd9b410'

In order to create a new instance of the same dictionary, you need to use the copy() member of a dict:

>>> hex(id(d))
'0x10bd95e60'
>>> w = d.copy()
>>> x = d.copy()
>>> y = d.copy()
>>> hex(id(w))
'0x10bd96128'
>>> hex(id(x))
'0x10bd95f80'
>>> hex(id(y))
'0x10bd96098'
dic['please_make_me_Foo']= appendic
dic['dont_make_him_Bar'] = appendic

appendic is an object - you are assigning a reference to the same object to both keys in dic. So when you change one, you change both.

Try this instead:

dic['please_make_me_Foo']= appendic.copy()
dic['dont_make_him_Bar'] = appendic.copy()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top