The differences between Python's assignment, slicing, shallow copy and deep copy

Keywords: Programming Python less

Preface

As a high-level language, Python is quite different from C/C + +. As for assignment, slice, shallow copy and deep copy, many people don't know them very well, which makes it easy to have unexpected results in some codes, and it's also hard to find the reasons. This article will talk about the difference and use of these kinds of situations, as easy to understand as possible, and will not involve the implementation principle of the underlying layer.

assignment

Assignment is that we assign the value of one variable to another through = which is equivalent to reference. The assignment here can be divided into several types

Assignment: assignment of immutable objects (within the cache)

In order to increase the running efficiency of the program, the parser of Python 3 implements the mechanism of integer number and string caching,

The cache range of integer numbers is [- 5, 256], that is, all variables with equal values and in the range of [- 5, 256] are the same object (this is controversial, some articles say [- 5, infinite]) The default cache length of string is  4096, that is, all string variables with equal value and length less than 4096 are the same object (this is controversial, many articles say that the cache is 20 bits)

# String assignment
str_a = str_b = 'hello' # Equivalent to str_a ='Hello 'and str_b = str_a
str_c = 'hello'
print(str_b is str_a) # Output: True
print(str_c is str_a) # Output: True. The output of True here is because of the caching mechanism. The values of str_c and str_a are equal, both are 'hello', and the length is within 20

# Integer assignment
int_a = int_b = 100 # It is equivalent to two statements: int UU a = 100 and int UU B = int UU a
int_c = 10 * 10
print(int_b is int_a) # Output True
print(int_c is int_a) # Output True, where output True is the caching mechanism, because the values of int_c and int_a are equal, both are 100, and are in the range of [- 5, 256]
//Output:
True
True
True
True

Assignment: assignment of immutable objects (not in cache range)

# String assignment
str_a = str_b = 'a' * 4097 # Equivalent to str_a ='a '* 4097 and str_b = str_a
str_c = 'a' * 4097
print(str_b is str_a) # Output: True
print(str_c is str_a) # Output: False, False is output here because although str_c and str_a have the same value, the length is 4097, exceeding the maximum cache length of 4096

# Integer assignment
int_a = int_b = 1000 # It is equivalent to two statements: int UU a = 100 and int UU B = int UU a
int_c = 10 * 10 * 10
print(int_b is int_a) # Output True
print(int_c is int_a) # Output False. The output False here is because although the values of int_c and int_a are equal, both are 1000, they are beyond the cache range [- 5, 256]
//Output:
True
False
True
False

Assignment: assignment of variable objects

This situation is equivalent to a complete reference, "shallower copy than a shallow copy". For example, if list a is a list, assign list a to list B, as long as it is not reassigned to list a or list B (list a = XXX or list B = YYY), either through list a or through List B (add, delete, change...) , the other object will also change (that is, list a and list B will always be the same object without performing the assignment again)

list_a = list_b = [1, 2, 3] # It is equivalent to the two statements of list a = [1, 2, 3] and list B = list a
list_c = [1, 2, 3]
print(list_b is list_a) # Output True, modify list a will affect List B, and vice versa
print(list_c is list_a) # Output False, so modifying list a will not affect list C, and vice versa
# Note: the term "shallower copy than shallower copy" here refers to the shallower copy. It can also be said that there is no copy at all, just a reference
# In the shallow copy, the modification of itself will not affect the other (only the operation of the variable sub element itself), while the modification of assignment in either case will affect the other (the assignment and shallow copy here are for the variable object)
//Output:
True
False

Section

Slicing is the operation of extracting part of an object. The object obtained by slicing is different from the original object, but its child elements may be the same object. Here are several cases to illustrate that slicing is equivalent to shallow copy

Slice: modification or addition or deletion of "is a child of an immutable object" does not affect another object

list_a = [1, 2, 3, 4]
list_b = list_a[:] # Complete slice
print(list_b is list_a) # Output False, list a and list B are different objects
list_a.append(5) # Append element to object list a
print(list_a) # Output [1, 2, 3, 4, 5]
print(list_b) # Output [1, 2, 3, 4]
print(list_a is list_b) # Output False, not the same object
//Output:
False
[1, 2, 3, 4, 5]
[1, 2, 3, 4]
False

Slice: operation on "is a child of a mutable object" affects another object

list_a = [1, 2, [3], 4] # Unlike the above, list a [2] is a mutable object
list_b = list_a[:] # Complete slice
print(list_b is list_a) # Output False, list a and list B are different objects
list_a[2].append(44) # Append the variable sub element. Note that the operation is to append the sub element itself
# list_a[2][0] = 33 # Modify the sub element of the variable sub element. Note that the sub element of the sub element itself is modified, not the sub element of list a
# del list_a[2][-1] # Delete the sub element of the variable sub element. Note that the sub element of the sub element itself is deleted, not the sub element of list a
print(list_a) # Output [1, 2, [3, 44], 4]
print(list_b) # Output [1, 2, [3, 44], 4]
print(list_b is list_a) # Output False, list a and list B are different objects
print(list_a[2] is list_b[2]) # Output True. In slice operation, variable sub elements are equivalent to assignment operation, i.e. list [a [2] and list [b [2] are the same object


# Note: in this case, there is no distinction between complete slicing and incomplete slicing. As long as the sub elements obtained by slicing are mutable objects, this situation is satisfied. The following code is an example of incomplete slicing, the same as that of complete slicing
list_c = list_a[:3] # Incomplete slicing, but the list [C [2] obtained by slicing is a variable object
print(list_c is list_a) # Output False, list a and list C are different objects
list_a[2][0] = 33 # Modify the child element of the variable child element. Note that it is the child element of the child element itself, not the child element of list UA
print(list_a) # Output [1, 2, [33, 44], 4]
print(list_c) # Output [1, 2, [33, 44]]
print(list_c is list_a) # Output False, list a and list C are different objects
print(list_a[2] is list_c[2]) # Output True. In slice operation, variable sub elements are equivalent to assignment operation, i.e. list UA [2] and list UC [2] are the same object

//Output:
False
[1, 2, [3, 44], 4]
[1, 2, [3, 44], 4]
False
True
False
[1, 2, [33, 44], 4]
[1, 2, [33, 44]]
False
True

Copy

Compared with the above assignment and slicing, the copy operation here refers to the copy operation through the copy module

Shallow copy

The shallow copy is implemented by the copy.copy(source) method (some objects themselves will provide the copy method, such as list.copy). The copied object and the original object may be the same object. If the copied object is a variable object, its child elements may be the same object

  • The shallow copy of immutable object is equivalent to the deep copy, similar to the assignment operation. Please refer to the assignment description above. Unlike the assignment, the copied object and the original object are the same object
import copy
a = 'hello'
b = copy.copy(a)
print(b is a) # Output True, same as assignment

c = 'a' * 4097
d = copy.copy(c)
print(d is c) # Output True, different from the assignment, has exceeded the cache range, but it is still the same. This situation can be compared to the assignment operation without cache range (positive cache)
//Output:
True
True
  • A shallow copy of a mutable object is equivalent to a complete slice. The resulting object is different from the original object, but its child elements may be the same object
import copy
a = [1, [2, [3, [4]]]]
b = copy.copy(a)
print(b is a) # Output False, the object obtained by shallow copy of variable object is different from the original object
print(b[0] is a[0]) # True, the immutable child elements of the object obtained by shallow copying of the mutable object are the same object, which is the same as the deep copy
print(b[1] is a[1]) # True, a shallow copy of a mutable object results in the same mutable child element of the object, which is different from a deep copy

# The following two are the same as the above two. They correspond one by one, but at a deeper level
print(b[1][0] is a[1][0]) # True
print(b[1][1] is a[1][1]) # True

b[0] = 111 # Modify b[0] directly
b.append(3434) # Append b
print(a, b) # Here a and b are different

b[1][0] = 22 # Modify the child element b[1][0] directly
b[1].append(5) # Append sub element b[1]
print(b[1], a[1]) # b[1] and a[1] remain the same here
//Output:
False
True
True
True
True
([1, [2, [3, [4]]]], [111, [2, [3, [4]]], 3434])
([22, [3, [4]], 5], [22, [3, [4]], 5])

Deep copy

Deep copy is implemented by \\\\\\\\\\\\\\\\\\.

  • Deep copy of immutable objects
import copy
a = 'a' * 10000
b = copy.deepcopy(a)
print(b is a) # Output True, the object obtained by deep copying immutable object is the same as the original object
//Output:
True
  • Deep copy of mutable objects
import copy
c = [1, [2, [3, [4]]]]
d = copy.deepcopy(c)
print(d is c) # Output False, the object obtained by deep copy of the variable object is different from the original object
print(d[0] is c[0]) # Output True, the immutable child element of the object obtained by deep copy of the variable object is the same object
print(d[1] is c[1]) # Output False, the variable sub elements of the object obtained by deep copy of the variable object are different objects
# The following two are the same as the above two. They correspond one by one, but at a deeper level
print(d[1][0] is c[1][0]) # Output True
print(d[1][1] is c[1][1]) # Output False
//Output:
False
True
False
True
False

summary

  • Assigning immutable objects depends on whether there is a caching mechanism to determine whether they are the same object
  • Assignment mutable object is equivalent to reference, no copy at all
  • Slice is equivalent to light copy
  • Shallow copy of immutable objects, equivalent to deep copy
  • To make a shallow copy of a mutable object, modifying one directly will not affect the other, but modifying its mutable child elements will affect the other
  • The object obtained from the deep copy is irrelevant to the original object. Modifying one will not affect the other, which means any modification

Posted by amargharat on Fri, 10 Jan 2020 00:53:21 -0800