Reputation: 201
I recently came across a problem with slice. Check the following code:
def clean(l):
for i in range(len(l)):
l[i] = 0
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
clean(lst[:])
print lst
This code prints out [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.
It seems that l[i] = 0
inside the function has no effect. So I guess that Python is making a copy of the list when passing a slice into the function... then I did another test...
def clean(l):
for i in range(len(l)):
print id(l[i])
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
clean(lst)
print
clean(lst[:])
What surprises me is that the output is as follows:
140373225703016
140373225702992
140373225702968
140373225702944
140373225702920
140373225702896
140373225702872
140373225702848
140373225702824
140373225702800
140373225703016
140373225702992
140373225702968
140373225702944
140373225702920
140373225702896
140373225702872
140373225702848
140373225702824
140373225702800
This says that Python is not making a copy of the list but of the reference... This becomes strange. Since the reference inside the function is still pointing to the original list object, why setting the value to 0 has no effect?
Upvotes: 3
Views: 254
Reputation: 4951
you are printing the ids of the integers. integer have always the same ids.
>>> id(9)
5126256
>>> id(9)
5126256
>>> id(123)
5126872
>>> id(122+1)
5126872
>>>
Upvotes: 0
Reputation: 2290
You shouldn't use id
or is
with integers or strings. Python reserves the option to intern these values, which is to say that it does things like cache small integers and strings for performance reasons so that even two values that should theoretically be different objects may have the same id.
When these things are interned is left up to the implementation, so it should be treated as undefined behavior.
http://me.veekun.com/blog/2012/03/24/python-faq-equality/
Upvotes: 0
Reputation: 365747
First, you can have two separate lists that reference the same elements:
>>> a, b, c = object(), object(), object()
>>> lst0 = [a, b, c]
>>> lst1 = list(reversed([c, b, a]))
>>> id(lst0), id(lst1)
(4418696712, 4399675656)
>>> id(lst0[0]), id(lst1[0])
(4298830320, 4298830320)
And if you make one list from the other, that's usually the case. Slicing a list with old_list[:]
, constructing a new list with list(old_list)
, copying it with copy.copy(old_list)
, etc.—all of these create a shallow copy. If you want a 2-level copy, you have to do it explicitly—e.g., [copy.copy(x) for x in old_lst]
. If you want an all-the-way-down deep copy, use copy.deepcopy(old_lst)
.
So, when you check the id
s of the elements of the two lists, you shouldn't be surprised to see them the same. Ashwini Chaudhary's answer explains the consequences of this.
On top of that, Python is always allowed to "intern" immutable objects like integers, strings, even tuples and frozen sets.* Unlike, e.g., Java, you can control this by using new
or not, in Python it's entirely up to the implementation, and you should never count on getting a new immutable object when you ask for one.
CPython only does this for a small set of values, but that includes a range of "tiny integers" specified at compile time, which default to -6 through 255 in 2.7. So, even if you did a deep copy—or even if you tried to create brand-new integer objects (e.g., int(x*2.0+0.1)//2
), you would likely still get the same objects again.
On top of that, for literals of built-in immutable types, the compiler may merge two identical literals, so by the time it gets to the interpreter, what looked like two different equal values in the source code are actually a single constant value in the bytecode. For example, at the interactive interpreter (which compiles each statement separately), try this:
>>> 123456 is 123456
True
>>> a = 123456
>>> a is 123456
False
You aren't guaranteed to get those results, but that's what I get with CPython 2.7 and 3.4 on my laptop. But in a function, since a function definition is a single compound statement that's all compiled together:
>>> def f():
... a = 123456
... return a is 123456
>>> f()
True
In general, you also shouldn't count on not getting a new immutable object—except in the special case of a handful of builtin constants, like None
.
Finally, be careful using id
for tests. The id
is only guaranteed to be unique during the lifetime of an object. So, it's perfectly valid for a Python implementation to return the same id
for two objects if the first one has already gone away before the second was created. (In CPython, the id
is the address of the underlying PyObject*
, so if the same memory gets pulled off the object free list and reused, you get the same id
.)
* In theory, it could even presumably intern your own custom objects if it knew they were immutable… but since there's no way it can know they're immutable, that's not going to happen.
Upvotes: 3
Reputation: 250961
Slicing returns a new container when applied to lists, but the inner items are still the same. When you assign 0
to an index on a sliced list, you're changing value of that new container not the original list.
>>> lis = [1, 2, 3]
>>> lis_1 = lis[:]
>>> lis_1 is lis # Outer lists are different
False
>>> lis_1[0] is lis[0]
True
>>> lis_1[0] = 10 # This assignment affects only `lis_1` not `lis`.
>>> lis
[1, 2, 3]
>>> lis_1
[10, 2, 3]
>>>
The above lists contain only immutable items, when your list contains mutable items and you perform an in-place operation on that item then the changes can be seen in all the lists:
>>> lis = [[1], 2, 3]
>>> lis_1 = lis[:]
>>> lis_1 is lis #Outer container are different
False
>>> lis[0] is lis_1[0] #But inner lists are same.
True
>>> lis[0].append(10)
>>> lis
[[1, 10], 2, 3]
>>> lis_1
[[1, 10], 2, 3]
From docs:
Some objects contain references to other objects; these are called containers. Examples of containers are
tuples
,lists
anddictionaries
. The references are part of a container’s value. In most cases, when we talk about the value of a container, we imply the values, not the identities of the contained objects; however, when we talk about the mutability of a container, only the identities of the immediately contained objects are implied. So, if an immutable container (like a tuple) contains a reference to a mutable object, its value changes if that mutable object is changed.
Upvotes: 5