Difference between a[:] = b and a = b[:]? (Python) - python

I was asked this for a coding test and didn't know the answer. Anyone have any ideas?

[:] is the slice operator.
When it's on the left side, it overwrites the contents of the list without creating a new reference.
When it's on the right side, it creates a copy of the list with the same contents.

a = b[:] calls either __getslice__ or __getitem__ on b and assigns the result to a. In nearly all cases (e.g. lists, tuples, and other sequence types), this makes a shallow copy of the sequence; I don't know of any classes that don't implement that behavior, but you could have a user-defined type that did something different. Any other objects that previously referred to the old value of a will continue to refer to that old value.
a[:] = b, on the other hand, calls __setslice__ or __setitem__ to replace a subset of the elements of a with those of the sequence b. In this case, if the sequence type of a is well-behaved, this will replace the entirety of a, since the range : without endpoints indicates the entire sequence. The difference here is that immutable types, such as tuples, will not allow you to perform __setslice__ (e.g. by throwing a TypeError exception). Any other objects that previously referred to a will also be updated, since the underlying object is being modified.
For mutable types such as list, the result of a = b[:] will be identical to a[:] = b, in that a will be a shallow copy of b; for immutable types such as tuple, a[:] = b is invalid. For badly-behaved user-defined types, all bets are off. There's also a difference in what happens to other objects that referred to the same object as a -- with a = b[:], they refer to the original value (a), but with a[:] = b, they refer to the modified object (shallow copy of b).

In both cases you end up the list a being a copy of the list b. But the method used to accomplish this has changed.
a[:] = b modifies the list a so that it has the same elements as b
a = b[:] produces a new list which is a copy of b and replaces the list a
The difference is whether we've modified an existing list or created a new one.
To see the difference:
a = range(3)
b = range(4)
c = a # c and a now share the same list
a[:] = b
print "a", a
print "b", b
print "C", c
All three lists will print out the same. C and a share the same object, so when a was modified so was c
a = range(3)
b = range(4)
c = a # c and a now share the same list
a = b[:]
print "a", a
print "b", b
print "C", c
Now c will not print out the same as a. After the assignment, a and c did not share the same object.
Speedwise, a[:] = b is probably a little faster than a = b[:]. The first form doesn't have to create a new list object, it can merely modify the existing list. A big part of this is that it can reuse the memory already owned by the list rather then allocating new memory.

Related

Altering an immutable object?

def add_alpha(tuple_in):
list_out = []
for t in tuple_in:
t.append(10)
a = ([1,1], [2,2], [3,3])
b = a
print(id(a), id(b))
add_alpha(a)
print(id(a), id(b))
print(b)
The above example shows that, even though a is an immutable object, we can technically alter its elements. How is that so? Wouldn't it make more sense if a was assigned a new id, so that we are not changing b as well?
There are two separate misconceptions here.
Tuples can have mutable elements
a is immutable with respect to which lists a[0], a[1] and a[3] point to.
In other words
l = a[0]
# do something (that does not reassign a)
print(l is a[0])
will always print True.
However, you can still mutate a[0]. A list cannot know that it is in some tuple and shall now be resistant to mutations.
If you issued
l = [1, 2, 3]
t = (l, 'foo')
you would expect l.append(1) to work fine. (And it does.)
Assignment never copies data
Python names work like labels for objects in memory. Assigning another name creates a new label, nothing more. When you issue
a = Foo() # some mutable object, or with mutable elements
b = a
mutate(a) # mutate a or one of its elements
the change will be seen across all names (a and b), because there only ever was one mutable object in memory. a = b didn't copy anything.

.pop to remove items from lists causes weird problems with variable scopes in python [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 2 years ago.
This one is making me absolutely crazy, so any help would be much appreciated. I have a program where I'm iterating through a list in a function. Here's a toy model of the problem:
masterList = ["person","woman","man","camera","television"]
workingList = masterList
def removeItem (workingList):
item = workingList.pop(2)
print("Test 1:",workingList)
removeItem(workingList)
print("Test 2:", workingList)
print("Test 3:", masterList)
As expected, "Test 1" prints out the list with an item removed.
However, "Test 2" also prints out the list with an item removed. I wasn't expecting that, but no matter. That's not the real problem, but I'm sure there's something I don't understand here about variable scopes and variable shadowing.
No the real problem is "Test 3", which as you can see, is printing out the masterList, which shouldn't even be touched by the removeItem function or the pop function within it. And yet, it too is printing out the list with an item removed.
How can this be happening, and how can I prevent it?
Thanks so much!
Cheers,
Ari
Python lists are mutable objects.
m = list([1, 2, 3])
n = m
a = 5
b = a
id(a) == id(b)
# id() return "identity" of the object.
# True, Both a and b are references to the same object.
id(m) == id(n)
# True, Both m and n are references to the same object.
b = b + 2
id(a) == id(b)
# False, a new object on separate location is created, which b will point.
n.pop()
id(m) == id(n)
# True, the object is mutated, and m and n still references to the same object.
Since, python lists are mutable, m and n will still be reference to the same object after mutation. Whereas, for immutable objects like int, a new object will be created and the identifier will refer to the new object.
Gist is, in your scenario, there has been only one object since python lists are mutable.
However, if you need the original list unchanged when the new list is modified, you can use the copy() method.
new_list = original_list.copy()
The ids of new_list and original_list is different.
Learn here about mutability in detail: https://medium.com/#meghamohan/mutable-and-immutable-side-of-python-c2145cf72747.
You have to make a copy of the masterList, otherwise workingList is nothing more than a reference.
If your list does not contain other containers (which yours doesn't), then a shallow copy is sufficient. There are numerous ways to make a shallow copy but slicing is the most optimal.
masterList = ["person","woman","man","camera","television"]
workingList = masterList[:] #copy via slice
a bunch of other ways to make a shallow copy
workingList = masterList * 1
workingList = masterList.copy()
workingList = list(masterList)
workingList = [*masterList]
import copy
workingList = copy.copy(masterList)
If you have a list that does possess something that holds a reference (like other containers, classes, etc), then you need to make a deepcopy.
import copy
a = [[1, 2, 3], ['a', 'b', 'c']]
b = copy.deepcopy(a)
Looks like I figured it out. The two lists are actually the same list unless you use list.copy()
So replacing the top two lines with:
masterList = ["person","woman","man","camera","television"]
workingList = masterList.copy()
makes everything perform as expected! Well, I still can't say I understand how the variable scopes work in their entirety, but at least this solves the major problem.

Uniting 2 sets works not the way I expected in python [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 4 years ago.
When I ran this script (Python v2.6):
a = [1,2]
b = a
a.append(3)
print a
>>>> [1,2,3]
print b
>>>> [1,2,3]
I expected print b to output [1,2]. Why did b get changed when all I did was change a? Is b permanently tied to a? If so, can I make them independent? How?
Memory management in Python involves a private heap memory location containing all Python objects and data structures.
Python's runtime only deals in references to objects (which all live in the heap): what goes on Python's stack are always references to values that live elsewhere.
>>> a = [1, 2]
>>> b = a
>>> a.append(3)
Here we can clearly see that the variable b is bound to the same object as a.
You can use the is operator to tests if two objects are physically the same, that means if they have the same address in memory. This can also be tested also using the id() function.
>>> a is b
>>> True
>>> id(a) == id(b)
>>> True
So, in this case, you must explicitly ask for a copy.
Once you've done that, there will be no more connection between the two distinct list objects.
>>> b = list(a)
>>> a is b
>>> False
Objects in Python are stored by reference—you aren't assigning the value of a to b, but a pointer to the object that a is pointing to.
To emulate assignation by value, you can make a copy like so:
import copy
b = copy.copy(a)
# now the code works as "expected"
Be aware this has performance disadvantages.
In the case of an array, there's a special method that relies on slices:
b = a[:]
# code also works as expected here
Update– In addition to this, with some objects you can use the constructor—this includes lists:
b = list(a)
Short answer - Pointers.
When you type b = a it is setting b to look at the same array that a looks at. You have to make a new array with copies of the elements to separate them. In this case, something like b = [n for n in a] would work fine. For more complex operations you may want to check out http://docs.python.org/library/copy.html.
You might want to look at this link. The problem you have here is a and b both point to the same memory location, so changing one changes the other. Instead, you want to do something like this:
a = [1,2]
b = list(a)
a is a pointer to the list [1,2].
When you do the assignment b = a the value of b is the address of the list [1,2].
So when you do a.append(3) you are not actually changing a, you are changing the list that a points to. Since a and b both point to the same list, they both appear to change when you modify the other.
If you simply want to copy the contents of list a to b, instead of making b a pointer to a:
b = a[:]
Using the slice operator will copy the contents of the list into b such that you example would become:
a = [1,2]
b = a[:]
a.append(3)
print a
>>>> [1,2,3]
print b
>>>> [1,2]

Chained list assignment in python [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 4 years ago.
When I ran this script (Python v2.6):
a = [1,2]
b = a
a.append(3)
print a
>>>> [1,2,3]
print b
>>>> [1,2,3]
I expected print b to output [1,2]. Why did b get changed when all I did was change a? Is b permanently tied to a? If so, can I make them independent? How?
Memory management in Python involves a private heap memory location containing all Python objects and data structures.
Python's runtime only deals in references to objects (which all live in the heap): what goes on Python's stack are always references to values that live elsewhere.
>>> a = [1, 2]
>>> b = a
>>> a.append(3)
Here we can clearly see that the variable b is bound to the same object as a.
You can use the is operator to tests if two objects are physically the same, that means if they have the same address in memory. This can also be tested also using the id() function.
>>> a is b
>>> True
>>> id(a) == id(b)
>>> True
So, in this case, you must explicitly ask for a copy.
Once you've done that, there will be no more connection between the two distinct list objects.
>>> b = list(a)
>>> a is b
>>> False
Objects in Python are stored by reference—you aren't assigning the value of a to b, but a pointer to the object that a is pointing to.
To emulate assignation by value, you can make a copy like so:
import copy
b = copy.copy(a)
# now the code works as "expected"
Be aware this has performance disadvantages.
In the case of an array, there's a special method that relies on slices:
b = a[:]
# code also works as expected here
Update– In addition to this, with some objects you can use the constructor—this includes lists:
b = list(a)
Short answer - Pointers.
When you type b = a it is setting b to look at the same array that a looks at. You have to make a new array with copies of the elements to separate them. In this case, something like b = [n for n in a] would work fine. For more complex operations you may want to check out http://docs.python.org/library/copy.html.
You might want to look at this link. The problem you have here is a and b both point to the same memory location, so changing one changes the other. Instead, you want to do something like this:
a = [1,2]
b = list(a)
a is a pointer to the list [1,2].
When you do the assignment b = a the value of b is the address of the list [1,2].
So when you do a.append(3) you are not actually changing a, you are changing the list that a points to. Since a and b both point to the same list, they both appear to change when you modify the other.
If you simply want to copy the contents of list a to b, instead of making b a pointer to a:
b = a[:]
Using the slice operator will copy the contents of the list into b such that you example would become:
a = [1,2]
b = a[:]
a.append(3)
print a
>>>> [1,2,3]
print b
>>>> [1,2]

When are objects copied in Python?

I am trying to understand when are objects copied in Python.
The easy case is explicit copy, for example:
A = [ 1,2,3 ]
B = list(A) # this is a copy of A
Are there scenarios which implicitly copy an object? For example some languages using "pass by value" will copy objects used as functions arguments (I know that this is not the case in Python). Are there such examples of implicit copy in Python?
Hopefully it will better illustrate what is happening in your code:
>>> a = object()
>>> b = object()
>>> A = [a, b]
>>> B = list(A)
>>> A
[<object object at 0x1002b7090>, <object object at 0x1002b70a0>]
>>> B
[<object object at 0x1002b7090>, <object object at 0x1002b70a0>]
>>> A is B
False
What you can see is list() actually creates a new instance of a list based on A. This is why A is B returns false. However the contents of both list are identical
In practical terms, any indirect assignment of an immutable object can be considered a copy. For example:
a = "abc"
b = a
a += "def"
print b
--> "abc"
Technically, there is no copying occurring. When you add to a, you are actually creating a new object and assigning the variable name "a" to it while the variable "b" continues to reference the original string.
As far as functions go, there is no copying that occurs in passed variables; however, the same rules apply for mutable and immutable arguments. If you were to pass an immutable object, any changes would only impact the local "copy".
list() is not doing anything special in your example.
list(iterable):
Return a list whose items are the same and in the same order as
iterable‘s items. iterable may be either a sequence, a container that
supports iteration, or an iterator object. If iterable is already a
list, a copy is made and returned, similar to iterable[:]. For
instance, list('abc') returns ['a', 'b', 'c'] and list( (1, 2, 3) )
returns [1, 2, 3]. If no argument is given, returns a new empty list,
[].

Categories

Resources