1
# importing copy module
import copy
 
# initializing list 1
li1 = [1, 2, [3, 5], 4]
 
# using copy for shallow copy
li2 = copy.copy(li1)
print("Before Edit:", id(li1[0]) == id(li2[0]))
li2[0] = "hi"
print(li2, "vs", li1)
print("After Edit:", id(li1[0]) == id(li2[0]))
print(id(li1[1]) == id(li2[1]))
print(id(li1) == id(li2))

print()

# using deepcopy for deepcopy
li3 = copy.deepcopy(li1)
print("Before Edit:", id(li1[0]) == id(li3[0]))
li3[0] = "bye"
print(li3, "vs", li1)
print("After Edit:", id(li1[0]) == id(li3[0]))
print(id(li1[1]) == id(li3[1]))
print(id(li1) == id(li3))

Based on the output below, id function in Python doesn't seem to be able to distinguish between a shallow copy and a deep copy. How can I prove that they are actually different from a memory/address standpoint?

Before Edit: True
['hi', 2, [3, 5], 4] vs [1, 2, [3, 5], 4]
After Edit: False
True
False

Before Edit: True
['bye', 2, [3, 5], 4] vs [1, 2, [3, 5], 4]
After Edit: False
True
False
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
rustlecho
  • 33
  • 1
  • 7
  • 1
    `print("Before Edit:", id(li1[0]) == id(li3[0]))` Both of these items are small integers, therefore they will have the same id due to _interning_. Try this again with a list of values that aren't small integers. https://stackoverflow.com/q/306313/494134 – John Gordon Feb 27 '23 at 16:28
  • @JohnGordon That has nothing to do with smallness / interning. – Kelly Bundy Feb 27 '23 at 17:22
  • @KellyBundy it is exactly about interning of popular immutable values: if we replace `2` with `257` or `['a']`, all three last lines should result in `False`, while for shallow copy it'll remain `False, True, False` (CPython). – STerliakov Feb 27 '23 at 17:59
  • Instead of modifying `list[0]`, try your example with modifying `list[2][0]`. The first will be copied in shallow and deep copies, the second only in deep copies. – treuss Feb 27 '23 at 18:16
  • 2
    @KellyBundy Hm, sorry, you're right: while with any mutable item like `['a']` the behaviour obviously adheres to what I describe, for many *built-in* immutable values `deepcopy` preserves the original `id` (is). This happens because of [dispatch table here](https://github.com/python/cpython/blob/main/Lib/copy.py#L177), Thanks, it was interesting! – STerliakov Feb 27 '23 at 18:24
  • @SUTerliakov Yep, that's the actual reason. – Kelly Bundy Feb 27 '23 at 18:28

2 Answers2

1

You could perhaps also usefully look at memory addresses:

import copy

my_list = [[1, 2, 3], [4, 5, 6]]
           
my_list_s = copy.copy(my_list)
my_list_d = copy.deepcopy(my_list)
           
print(f"original-> {id(my_list)} shallow-> {id(my_list_s)} deep->{id(my_list_d)}")
print(f"original-> {id(my_list[0])} shallow-> {id(my_list_s[0])} deep->{id(my_list_d[0])}")

which gives on my system:

original-> 2879112284480 shallow-> 2879112248064 deep->2879112098112
original-> 2879102420352 shallow-> 2879102420352 deep->2879112098048

Note that the shallow copy has a different address from the original but contains the same references as the original. The deepcopy contains different references.

user19077881
  • 3,643
  • 2
  • 3
  • 14
1

The main difference is in how the object items are processed. So you should be comparing the identifiers of the 3rd element (a list object). Python's integers are immutable so their id is the same for multiple copies of a given value

A = [1,2,[3,4]]
        
B = copy.deepCopy(A)
C = copy.copy(A)
                    
id(A[2]) # 140369269603272
id(B[2]) # 140369267423560
id(C[2]) # 140369269603272  (same as A[2])
Alain T.
  • 40,517
  • 4
  • 31
  • 51