Embedding Object reference to other objects, shallow/deepcopy python 3+ - python

As explained in the python docs for V3.7 (https://docs.python.org/3/library/copy.html)
A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
I tried to create a compound object as below by passing reference to a list to another list and as per the book I am reading, the new list L must store a reference to X and if X changes L must change too.
X = [1, 2, 3]
L = ['a', X, 'b']
However in my test run on IDLE, I can see that L contains the object [1,2,3] , hence does not store a reference to X, I tested this by changing X, which does not affect L:
>>> X = [1, 2, 3]
>>> L = ['a', X, 'b']
>>> X
[1, 2, 3]
>>> X=[1,2]
>>> L
['a', [1, 2, 3], 'b']
>>> X
[1, 2]
So my question is:
Does in python 3+ meaning of embedding reference to objects changed. If yes then does that mean shallow and deep copying does not differentiate any more, meaning they both make deep copies of the objects, as my tests show below and seems contrary to the documentation.
>>> L.copy()
['a', [1, 2, 3], 'b']
>>> import copy
>>> copy.deepcopy(L)
['a', [1, 2, 3], 'b']
>>>

X=[1,2] rebinds the name X to a new object. Modifying the original object works as expected:
>>> import copy
>>> X=[1,2,3]
>>> L = [1, X, 2]
>>> L
[1, [1, 2, 3], 2]
>>> L_ = copy.copy(L)
>>> L_
[1, [1, 2, 3], 2]
>>> X.append('WOW') # modify here
>>> L, L_
([1, [1, 2, 3, 'WOW'], 2], [1, [1, 2, 3, 'WOW'], 2]) # the change is reflected in both objects

Related

Python List inserting independent reference variables [duplicate]

This question already has answers here:
How to deep copy a list?
(10 answers)
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
What is the best way to copy a list? [duplicate]
(7 answers)
Closed 1 year ago.
Let's assume that a is some reference variable and I want to put it into a list three times:
a = [1,2,3]
b = [a]*3
>>> b
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
So this still works as expected. But if I append an element to one of the reference variables it is reflected to all of them:
>>> b[0].append(4)
>>> b
[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
Does this happen because the * operator in the first code block only copies the references and the list only points to a? Do I need a for-loop in order to be able to have a list of independent reference variables (copies) n times? (where n is 3 in the example above)
How excactly does the * operator work in this instance?
With a for-loop and the append()-Method the behaviour is the same - are references also used here?:
>>> a = [1,2,3]
>>> b = []
>>> for i in range(2):
... b.append(a)
...
>>> b
[[1, 2, 3], [1, 2, 3]]
>>> b[0].append(4)
>>> b
[[1, 2, 3, 4], [1, 2, 3, 4]]
When inserting copies the behaviour is as expected:
>>> a = [1,2,3]
>>> b = []
>>> for i in range(2):
... b.append(a.copy())
...
>>> b
[[1, 2, 3], [1, 2, 3]]
>>> b[0].append(4)
>>> b
[[1, 2, 3, 4], [1, 2, 3]]
Is there a shorter, more convenient way to do this than to loop with .append() and .copy() as I did above?
My Python-Version is Python 3.8.6
Note that my questions go beyond just using deep_copy() as it is about inserting elements more often in a list and the *-operator - I know what deep_copy() is!

Why does appending a list by itself create an infinite list

l = [1, 2]
l.append(l)
>>>l
[1, 2, [...]] #l is an infinite list
Why does this create an infinite list instead of creating:
l = [1, 2]
l.append(l)
>>>l
[1, 2, [1, 2]]
When you do:
l.append(l)
a reference to list l is appended to list l:
>>> l = [1, 2]
>>> l.append(l)
>>> l is l[2]
True
>>>
In other words, you put the list inside itself. This creates an infinite reference cycle which is represented by [...].
To do what you want, you need to append a copy of list l:
>>> l = [1, 2]
>>> l.append(l[:]) # Could also do 'l.append(list(l))' or 'l.append(l.copy())'
>>> l
[1, 2, [1, 2]]
>>> l is l[2]
False
>>>
Easy, because each object will have a reference to itself in the third element. To achieve [1, 2, [1, 2]] then use a copy of the list.
l.append(l[:])

Equal strings auto updating (List object reference)

I recently did an exam for university and I got asked what would be the output of this program:
def fun(x):
y=x
x.append(4)
print(str(x)+" "+str(y))
fun(["one","two",3,5.0])
I answered that the y list would be ["one","two", 3,5.0] and after appending 4 to it, the x list would be equal to the same but with a 4 at the end of it. To my surprise, when I printed both lists, they were equal even though the x list update was performed after establishing an equality between both lists. Why did this happen?
Thank you
You have given reference if list x to y. So any change in list x would also affect list y.
y=x
For example:
>>> x = ["one","two",3,5.0]
>>> y = x
>>> x[3] = 4
>>> x
['one', 'two', 3, 4]
>>> y
['one', 'two', 3, 4]
Here both x and y have same identity.
>>> x is y
True
>>> id(x)
3073118540L
>>> id(y)
3073118540L
You can better understand this using swampy module:
>>> from swampy.Lumpy import Lumpy
>>> lump = Lumpy()
>>> x = ["one","two",3,5.0]
>>> y = x
>>> x[3] = 4
>>> lump.object_diagram()
What you were expecting can be achieved by copying the list x to list y like this:
>>> x = ["one","two",3,5.0]
>>> y = x[:]
>>> x.pop()
5.0
>>> x
['one', 'two', 3]
>>> y
['one', 'two', 3, 5.0]
So by copying the content from x to y, they don't hold the same identity:
>>> id(x)
3073240428L
>>> id(y)
3073240588L
>>> x is y
False
Using swampy:
>>> from swampy.Lumpy import Lumpy
>>> lump = Lumpy()
>>> x = ["one","two",3,5.0]
>>> y = x[:]
>>> lump.draw_object()
>>> lump.object_diagram()
For better explanation visit here How do I copy an object in Python?
Actually x and y are labels that reference to object so when you assign y=x you crate 2 reference to one object , so when you change one of them you change the main object .
Also you may note that x , y are local variables when you made inplace changes like append you changed the main object , but if you use assignment python create a new object :
>>> def fun(x):
... y=x
... x=x+[3]
... print(str(x)+" "+str(y))
...
>>> fun(["one","two",3,5.0])
['one', 'two', 3, 5.0, 3] ['one', 'two', 3, 5.0]
in-place changes to objects do not classify names as locals; only actual name
assignments do. For instance, if the name L is assigned to a list at the top level of a
module, a statement L = X within a function will classify L as a local, but L.append(X)
will not. In the latter case, we are changing the list object that L references, not L itself—
L is found in the global scope as usual, and Python happily modifies it without requiring
a global (or nonlocal ) declaration. As usual, it helps to keep the distinction between
names and objects clear: changing an object is not an assignment to a name.(from learning python by mark lutz)
class A:
global L
L=[1,2]
def b(self):
L=[0,0]
return L
def c(self):
L.append(5)
return L
a=A()
print a.b()
print a.c()
result :
[0, 0]
[1, 2, 5]
Because list are mutable objects. see Python Data Model
In [1]: a = [1]
In [3]: b = a
In [4]: b
Out[4]: [1]
In [5]: b.append(2)
In [6]: a
Out[6]: [1, 2]
In [7]: b
Out[7]: [1, 2]
In [8]: id(a), id(b)
Out[8]: (140260765233376, 140260765233376)
Because the name y is bound to the same list as x
y = x
This is a great drawing of how that looks:
x y
| /
| /
["one", "two", 3, 5.0]
x.append(4)
x y
| /
| /
["one", "two", 3, 5.0, 4]
You can try following example. It will help you to get differences between assignment operator and method like copy(shallow), deepcopy.
>>> import copy
>>> l1 = [1,2, [1,2]]
>>> l1
[1, 2, [1, 2]]
#Create l2, l3, l4 by copy, deepcopy method and normal assignment.
>>> l2 = copy.copy(l1)
>>> l3 = copy.deepcopy(l1)
>>> l4 = l1
>>> l2
[1, 2, [1, 2]]
>>> l3
[1, 2, [1, 2]]
>>> l4
>>> [1, 2, [1, 2]]
#-----------------------Now Append value to l1
>>> l1.append(9)
>>> l1
[1, 2, [1, 2], 9]
>>> l2
[1, 2, [1, 2]]
>>> l3
[1, 2, [1, 2]]
>>> l4
>>> [1, 2, [1, 2], 9]
#-----------------------Now Append value to l1[2]
>>> l1[2].append(5)
>>> l1
[1, 2, [1, 2, 5], 9]
>>> l2
[1, 2, [1, 2, 5]]
>>> l3
[1, 2, [1, 2]]
>>> l4
>>> [1, 2, [1, 2, 5], 9]
#------------------------

python list index is the same list [duplicate]

This question already has answers here:
Confusing [...] List in Python: What is it?
(9 answers)
Closed 9 years ago.
I just seen a output like below - just want to know what is happening here.
>>> l = [1,2,3,4]
>>> l[0]=l
>>> l
[[...], 2, 3, 4]
Why the l[0] value has displayed like this? Can anyone explain me why this behavior.
I was thinking it'd return like, [[1,2,3,4], 2, 3, 4].
Cheers,
Kalai
It shows the ... because otherwise it would have to infinitely recurse.
A list object in Python is a pointer to a list- assigning it like l[0] = l doesn't make a copy. For instance, try
l1 = [1, 2, 3, 4]
l2 = [1, 2]
l2[0] = l1
print l2
# [[1, 2, 3, 4], 2]
l2[0].append(5)
print l1
# [1, 2, 3, 4, 5]
Notice that even though you never changed l1 explicitly, it has now been appended to.
Therefore, when you place a list within itself, that item of the list is still a link to the entire list. After your code above, try doing:
l[1] # ==> 2
l[0][1] # ==> 2
l[0][0][1] # ==> 2
Use a copy of the list to avoid infinite recursion:
In [10]: l = [1,2,3,4]
In [11]: l[0] = l[:]
In [12]: l
Out[12]: [[1, 2, 3, 4], 2, 3, 4]
If you would have used a PrettyPrinter, the output would had been self explanatory
>>> l = [1,2,3,4]
>>> l[0]=l
>>> l
[[...], 2, 3, 4]
>>> pp = pprint.PrettyPrinter(indent = 4)
>>> pp.pprint(l)
[<Recursion on list with id=70327632>, 2, 3, 4]
>>> id(l)
70327632

How do I remove the first item from a list?

How do I remove the first item from a list?
[0, 1, 2, 3] → [1, 2, 3]
You can find a short collection of useful list functions here.
list.pop(index)
>>> l = ['a', 'b', 'c', 'd']
>>> l.pop(0)
'a'
>>> l
['b', 'c', 'd']
>>>
del list[index]
>>> l = ['a', 'b', 'c', 'd']
>>> del l[0]
>>> l
['b', 'c', 'd']
>>>
These both modify your original list.
Others have suggested using slicing:
Copies the list
Can return a subset
Also, if you are performing many pop(0), you should look at collections.deque
from collections import deque
>>> l = deque(['a', 'b', 'c', 'd'])
>>> l.popleft()
'a'
>>> l
deque(['b', 'c', 'd'])
Provides higher performance popping from left end of the list
Slicing:
x = [0,1,2,3,4]
x = x[1:]
Which would actually return a subset of the original but not modify it.
>>> x = [0, 1, 2, 3, 4]
>>> x.pop(0)
0
More on this here.
With list slicing, see the Python tutorial about lists for more details:
>>> l = [0, 1, 2, 3, 4]
>>> l[1:]
[1, 2, 3, 4]
you would just do this
l = [0, 1, 2, 3, 4]
l.pop(0)
or l = l[1:]
Pros and Cons
Using pop you can retrieve the value
say x = l.pop(0)
x would be 0
Then just delete it:
x = [0, 1, 2, 3, 4]
del x[0]
print x
# [1, 2, 3, 4]
You can also use list.remove(a[0]) to pop out the first element in the list.
>>>> a=[1,2,3,4,5]
>>>> a.remove(a[0])
>>>> print a
>>>> [2,3,4,5]
You can use list.reverse() to reverse the list, then list.pop() to remove the last element, for example:
l = [0, 1, 2, 3, 4]
l.reverse()
print l
[4, 3, 2, 1, 0]
l.pop()
0
l.pop()
1
l.pop()
2
l.pop()
3
l.pop()
4
If you are working with numpy you need to use the delete method:
import numpy as np
a = np.array([1, 2, 3, 4, 5])
a = np.delete(a, 0)
print(a) # [2 3 4 5]
There is a data structure called deque or double ended queue which is faster and efficient than a list. You can use your list and convert it to deque and do the required transformations in it. You can also convert the deque back to list.
import collections
mylist = [0, 1, 2, 3, 4]
#make a deque from your list
de = collections.deque(mylist)
#you can remove from a deque from either left side or right side
de.popleft()
print(de)
#you can covert the deque back to list
mylist = list(de)
print(mylist)
Deque also provides very useful functions like inserting elements to either side of the list or to any specific index. You can also rotate or reverse a deque. Give it a try!
Unpacking assignment:
You could use unpacking assignment as mentioned in PEP 3132.
Solution:
You should try unpacking like the following:
>>> l = [0, 1, 2, 3, 4]
>>> _, *l = l
>>> l
[1, 2, 3, 4]
Explanation:
As mentioned in PEP 3132:
This PEP proposes a change to iterable unpacking syntax, allowing to
specify a "catch-all" name which will be assigned a list of all items
not assigned to a "regular" name.
An example says more than a thousand words:
>>> a, *b, c = range(5)
>>> a
0
>>> c
4
>>> b
[1, 2, 3]
This works for me, instead of using pop like below, which of course will be 0, because the result/return value of pop
>>> x = [0, 1, 2, 3].pop(0)
>>> x
0
Using this below method will skip the first value:
>>> x = [0, 1, 2, 3][1:]
>>> x
[1, 2, 3]

Categories

Resources