Why is the dictionary key not changing - python

a = (1,2)
b = {a:1}
print(b[a]) # This gives 1
a = (1,2,3)
print(b[a]) # This gives error but b[(1,2)] is working fine
What I understood is python doesn't run garbage collector after a is changed to (1,2,3) as the tuple (1,2,3) is created as a new object and the tuple (1,2) is still being referenced in b.
What I didn't understood is why 'b' doesn't change the key after 'a' is changed

b = {a:1} creates a dictionary with the value of a as a key and 1 as a value. When you assign a value to a, you create a new value, and b retrain the old value as its key.
The following example, using id, may illustrate it:
>>> a = (1,2)
>>> b = {a:1}
>>> id(a)
139681226321288
>>> a = (1,2,3)
>>> id(a)
139681416297520
>>> id(b.keys()[0])
139681226321288

Integers, floats, strings, tuples in python are immutable. A dictionary would allow only those keys which would be hashable (immutable built-in objects are hashable). As #Mureinik correctly specified the reason behind the cause, I would give you another example where you can mutate the data by the process you followed above.
>>> l = [1,2,3]
>>> b = {'3' : l}
>>> b
{'3': [1, 2, 3]}
>>> l.append(5)
>>> l
[1, 2, 3, 5]
>>> b
{'3': [1, 2, 3, 5]}
But you cannot change the keys of a dictionary as they are hashed (only values can be updated). You either have to delete existing key-value pair or add new pair.

Related

How does variable copying in Python exactly work?

Why is it that:
>>> a = 1
>>> b = a
>>> a = 2
>>> print(a)
2
>>> print(b)
1
...but:
>>> a = [3, 2, 1]
>>> b = a
>>> a.sort()
>>> print(b)
[1, 2, 3]
I mean, why are variables really copied and iterators just referenced?
Variables are not "really copied". Variables are names for objects, and the assignment operator binds a name to the object on the right hand side of the operator. More verbosely:
>>> a = 1 means "make a a name referring to the object 1".
>>> b = a means "make b a name referring to the object currently referred to by a. Which is 1.
>>> a = 2 means "make a a name referring to the object 2". This has no effect on which object anything else that happened to refer to 1 now refers to, such as b.
In your second example, both a and b are names referring to the same list object. a.sort() mutates that object in place, and because both variables refer to the same object the effects of the mutation are visible under both names.
Think of the assigned variables as pointers to the memory location where the values are held. You can actually get the memory location using id.
a = 1
b = a
>>> id(a)
4298171608
>>> id(b)
4298171608 # points to the same memory location
a = 2
>>> id(a)
4298171584 # memory location has changed
Doing the same with your list example, you can see that both are in fact operating on the same object, but with different variables both pointing to the same memory location.
a = [3, 2, 1]
b = a
a.sort()
>>> id(a)
4774033312
>>> id(b)
4774033312 # Same object
in your first example you've reassigned a's value after making b's value a. so a and b carry different values.
the same would've occurred in your second example if you had reassigned a to a new sorted list instead of just sorting it in place.
a = [3,2,1]
b = a
a.sort()
print b
[1,2,3]
but...
a = [3,2,1]
b = a
sorted(a)
print b
[3,2,1]

what is the difference between del a[:] and a = [] when I want to empty a list called a in python? [duplicate]

This question already has answers here:
Different ways of deleting lists
(6 answers)
Closed 7 years ago.
Please what is the most efficient way of emptying a list?
I have a list called a = [1,2,3]. To delete the content of the list I usually write a = [ ]. I came across a function in python called del. I want to know if there is a difference between del a [:] and what I use.
There is a difference, and it has to do with whether that list is referenced from multiple places/names.
>>> a = [1, 2, 3]
>>> b = a
>>> del a[:]
>>> print(b)
[]
>>> a = [1, 2, 3]
>>> b = a
>>> a = []
>>> print(b)
[1, 2, 3]
Using del a[:] clears the existing list, which means anywhere it's referenced will become an empty list.
Using a = [] sets a to point to a new empty list, which means that other places the original list is referenced will remain non-empty.
The key to understanding here is to realize that when you assign something to a variable, it just makes that name point to a thing. Things can have multiple names, and changing what a name points to doesn't change the thing itself.
This can probably best be shown:
>>> a = [1, 2, 3]
>>> id(a)
45556280
>>> del a[:]
>>> id(a)
45556280
>>> b = [4, 5, 6]
>>> id(b)
45556680
>>> b = []
>>> id(b)
45556320
When you do a[:] you are referring to all elements within the list "assigned" to a. The del statement removes references to objects. So, doing del a[:] is saying "remove all references to objects from within the list assigned to a". The list itself has not changed. We can see this with the id function, which gives us a number representing an object in memory. The id of the list before using del and after remains the same, indicating the same list object is assigned to a.
On the other hand, when we assign a non-empty list to b and then assign a new empty list to b, the id changes. This is because we have actually moved the b reference from the existing [4, 5, 6] list to the new [] list.
Beyond just the identity of the objects you are dealing with, there are other things to be aware of:
>>> a = [1, 2, 3]
>>> b = a
>>> del a[:]
>>> print a
[]
>>> print b
[]
Both b and a refer to the same list. Removing the elements from the a list without changing the list itself mutates the list in place. As b references the same object, we see the same result there. If you did a = [] instead, then a will refer to a new empty list while b continues to reference the [1, 2, 3] list.
>>> list1 = [1,2,3,4,5]
>>> list2 = list1
To get a better understanding, let us see with the help of pictures what happens internally.
>>> list1 = [1,2,3,4,5]
This creates a list object and assigns it to list1.
>>> list2 = list1
The list object which list1 was referring to is also assigned to list2.
Now, lets look at the methods to empty an list and what actually happens internally.
METHOD-1: Set to empty list [] :
>>> list1 = []
>>> list2
[1,2,3,4,5]
This does not delete the elements of the list but deletes the reference to the list. So, list1 now points to an empty list but all other references will have access to that old list1.
This method just creates a new list object and assigns it to list1. Any other references will remain.
METHOD-2: Delete using slice operator[:] :
>>> del list1[:]
>>> list2
[]
When we use the slice operator to delete all the elements of the list, then all the places where it is referenced, it becomes an empty list. So list2 also becomes an empty list.
Well, del uses just a little less space in the computer as the person above me implied. The computer still accepts the variable as the same code, except with a different value. However, when you variable is assigned something else, the computer assigns a completely different code ID to it in order to account for the change in memory required.

Why does this empty dict break shared references?

I have found some Python behavior that confuses me.
>>> A = {1:1}
>>> B = A
>>> A[2] = 2
>>> A
{1: 1, 2: 2}
>>> B
{1: 1, 2: 2}
So far, everything is behaving as expected. A and B both reference the same, mutable, dictionary and altering one alters the other.
>>> A = {}
>>> A
{} # As expected
>>> B
{1: 1, 2: 2} # Why is this not an empty dict?
Why do A and B no longer reference the same object?
I have seen this question: Python empty dict not being passed by reference? and it verifies this behavior, but the answers explain how to fix the provided script not why this behavior occurs.
Here is a pictorial representation *:
A = {1: 1}
# A -> {1: 1}
B = A
# A -> {1: 1} <- B
A[2] = 2
# A -> {1: 1, 2: 2} <- B
A = {}
# {1: 1, 2: 2} <- B
# A -> {}
A = {} creates a completely new object and reassigns the identifier A to it, but does not affect B or the dictionary A previously referenced. You should read this article, it covers this sort of thing pretty well.
Note that, as an alternative, you can use the dict.clear method to empty the dictionary in-place:
>>> A = {1: 1}
>>> B = A
>>> A[2] = 2
>>> A.clear()
>>> B
{}
As A and B are still references to the same object, both now "see" the empty version.
* To a first approximation - similar referencing behaviour is going on within the dictionary too, but as the values are immutable it's less relevant.
Remember, variables in python act like labels. So, in the first example, you have a dictionary {1: 1, 2: 2}. That dictionary stays in memory. In the first example, A points to that dictionary, and you say B points to what A is pointing to (It won't point to the label A, but rather what the label A is pointing to).
In the second example, A and B are both pointing to this dictionary, but you point A to a new dictionary ({}). B stays pointing to the old dictionary in memory from the first example.
you are changing the dictionary A points to when you say A={} not destroying the old dictionary ... this sample should demonstrate for you
A={1:1}
print id(A)
B = A
print id(B)
B[2] = 5
print id(B)
print A
print id(A)
A = {}
print id(A)
It's about the difference between creating a new dictionary and changing an existing dictionary.
A[2] = 2
Is modifying the dictionary by adding a new key, the existing stuff is still part of that dictionary.
A = {}
This creates a totally new empty dictionary.
Think about it like this: A is the name of one object, then you make B a different name for that object. That's the first part, but then in the second code you make a new object and say ok that old object isn't called A anymore now this new object is called A.
B isn't pointing at A. B and A are both names for the same object, then names for two different objects.

Does tuple() copy the elements of the argument?

In python, does the built-in function tuple([iterable]) create a tuple object and fill it with copies of the elements of "iterable", or does it create a tuple containing references to the already existing objects of "iterable"?
tuple will iterate the sequence and copy the values. The underlying sequence will be not stored to actually keep the values, but the tuple representation will replace it. So yes, the conversion to a tuple is actual work and not just some nesting of another type.
You can see this happening when converting a generator:
>>> def gen ():
for i in range(5):
print(i)
yield i
>>> g = gen()
>>> g
<generator object gen at 0x00000000030A9B88>
>>> tuple(g)
0
1
2
3
4
(0, 1, 2, 3, 4)
As you can see, the generator is immediately iterated, making the values generate. Afterwards, the tuple is self-contained, and no reference to the original source is kept. For reference, list() behaves in exactly the same way but creates a list instead.
The behaviour that 275365 pointed out (in the now deleted answer) is the standard copying behaviour of Python values. Because everything in Python is an object, you are essentially only working with references. So when references are copied, the underlying object is not copied. The important bit is that non-mutable objects will be recreated whenever their value changes which will not update all previously existing references but just the one reference you are currently changing. That’s why it works like this:
>>> source = [[1], [2], [3]]
>>> tpl = tuple(source)
>>> tpl
([1], [2], [3])
>>> tpl[0].append(4)
>>> tpl
([1, 4], [2], [3])
>>> source
[[1, 4], [2], [3]]
tpl still contains a reference to the original objects within the source list. As those are lists, they are mutable. Changing a mutable list anywhere will not invalidate the references that exist to that list, so the change will appear in both source and tpl. The actual source list however is only stored in source, and tpl has no reference to it:
>>> source.append(5)
>>> source
[[1, 4], [2], [3], 5]
>>> tpl
([1, 4], [2], [3])
Yes. I think answer to both of your questions are a "yes". When you create a new tuple from an existing iterable, it will simply copy each item and add it to the new tuple object you are creating. Since variables in python are actually names referencing objects, the new tuple you are creating will actually hold references to the same objects as the iterable.
I think this question on variable passing will be helpful.
tuple([iterables]) will create a tuple object with the reference of the iterable. But, if the iterable is a tuple then it will return the same object else it will create a new tuple object initialized from the iterable items.
>>> a = (1,2)
>>> b = tuple(a)
>>> a is b
True
>>> c = [1,2]
>>> d = tuple(c)
>>> c is d
False
>>> c[0] is d[0]
True
>>> c[1] is d[1]
True
>>> type(c), type(d)
(<type 'list'>, <type 'tuple'>)
>>>
It will not copy or deep-copy the elements:
a = [{"key": "value"}]
x = tuple(a)
print x #=> ({"key": "value"},)
a[0]["key"] = "fish"
print x #=> ({"key": "fish"},)

Store reference to primitive type in Python?

Code:
>>> a = 1
>>> b = 2
>>> l = [a, b]
>>> l[1] = 4
>>> l
[1, 4]
>>> l[1]
4
>>> b
2
What I want to instead see happen is that when I set l[1] equal to 4, that the variable b is changed to 4.
I'm guessing that when dealing with primitives, they are copied by value, not by reference. Often I see people having problems with objects and needing to understand deep copies and such. I basically want the opposite. I want to be able to store a reference to the primitive in the list, then be able to assign new values to that variable either by using its actual variable name b or its reference in the list l[1].
Is this possible?
There are no 'primitives' in Python. Everything is an object, even numbers. Numbers in Python are immutable objects. So, to have a reference to a number such that 'changes' to the 'number' are 'seen' through multiple references, the reference must be through e.g. a single element list or an object with one property.
(This works because lists and objects are mutable and a change to what number they hold is seen through all references to it)
e.g.
>>> a = [1]
>>> b = a
>>> a
[1]
>>> b
[1]
>>> a[0] = 2
>>> a
[2]
>>> b
[2]
You can't really do that in Python, but you can come close by making the variables a and b refer to mutable container objects instead of immutable numbers:
>>> a = [1]
>>> b = [2]
>>> lst = [a, b]
>>> lst
[[1], [2]]
>>> lst[1][0] = 4 # changes contents of second mutable container in lst
>>> lst
[[1], [4]]
>>> a
[1]
>>> b
[4]
I don't think this is possible:
>>> lst = [1, 2]
>>> a = lst[1] # value is copied, not the reference
>>> a
2
>>> lst[1] = 3
>>> lst
[1, 3] # list is changed
>>> a # value is not changed
2
a refers to the original value of lst[1], but does not directly refer to it.
Think of l[0] as a name referring to an object a, and a as a name that referring to an integer.
Integers are immutable, you can make names refer to different integers, but integers themselves can't be changed.
There were a relevant discussion earlier:
Storing elements of one list, in another list - by reference - in Python?
According to #mgilson, when doing l[1] = 4, it simply replaces the reference, rather than trying to mutate the object. Nevertheless, objects of type int are immutable anyway.

Categories