Diferències
Ací es mostren les diferències entre la revisió seleccionada i la versió actual de la pàgina.
| Ambdós costats versió prèvia Revisió prèvia | |||
| info:cursos:pue:python-pcpp1:m1:4.1 [05/11/2023 21:34] – suprimit - edició externa (Unknown date) 127.0.0.1 | info:cursos:pue:python-pcpp1:m1:4.1 [05/11/2023 21:34] (actual) – ↷ Page moved from info:cursos:pue:python-pcpp1:4.1 to info:cursos:pue:python-pcpp1:m1:4.1 mate | ||
|---|---|---|---|
| Línia 1: | Línia 1: | ||
| + | = 4.1 Shallow and deep copy operations | ||
| + | == Copying objects using shallow and deep operations | ||
| + | In this module, you’ll learn how to copy Python objects. Specifically, | ||
| + | * object: label vs. identity vs. value; | ||
| + | * the id() function and the is operand; | ||
| + | * shallow and deep copies of the objects. | ||
| + | |||
| + | It’s hard to imagine writing a piece of Python code that performs any kind of data processing without making use of variables. As variables are fundamental elements that allow us to cope with objects, let's talk in detail about variables and objects, and possible ways of copying them. | ||
| + | |||
| + | When you spot the following clause: | ||
| + | <code python> | ||
| + | a_list = [ 1, 'New York', 100] | ||
| + | </ | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | (Note that an assignment statement is being used, so evaluation of the right side of the clause takes precedence over the left side.) | ||
| + | |||
| + | * At first, an object (a list in this example) is created in the computer' | ||
| + | * then the object is populated with other objects. Now our object has a value; | ||
| + | * finally a variable, which you should treat as a label or name binding, is created, and this label refers to a distinct place in the computer memory. | ||
| + | |||
| + | What is that object ' | ||
| + | |||
| + | The built-in '' | ||
| + | |||
| + | CPython implementation detail: This is the address of the object in the memory. Don’t treat it as an absolute memory address. | ||
| + | |||
| + | Run the code presented in the right pane to see how the strings are located in the memory. | ||
| + | |||
| + | <code python> | ||
| + | a_string = '10 days to departure' | ||
| + | b_string = '20 days to departure' | ||
| + | |||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | |||
| + | Remember that the memory addresses are different: | ||
| + | <code ; output> | ||
| + | a_string identity: 8466656 | ||
| + | b_string identity: 8466704 | ||
| + | </ | ||
| + | |||
| + | This function is rarely used in applications. More often you’ll use it to debug the code or to experiment while copying objects. The side effect of this infrequent use is that some developers forget about its existence and create their own variables titled '' | ||
| + | |||
| + | As a result, a variable called '' | ||
| + | |||
| + | When you have two variables referring to the same object, the return values of the id() function must be the same. | ||
| + | |||
| + | Run the code presented in the right pane to confirm our speculations: | ||
| + | <code python> | ||
| + | a_string = '10 days to departure' | ||
| + | b_string = a_string | ||
| + | |||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | <code ; output> | ||
| + | a_string identity: 8466704 | ||
| + | b_string identity: 8466704 | ||
| + | </ | ||
| + | |||
| + | In this example, we haven’t created a new list, but just created a new label that references the already created list. | ||
| + | |||
| + | This interesting behavior will be examined on the following pages. | ||
| + | |||
| + | === What is the difference between the ' | ||
| + | What should you do to compare two objects? | ||
| + | |||
| + | In order to compare two objects, you should start with the '' | ||
| + | |||
| + | In fact, two distinct objects holding the same values could be compared, and the result would be ' | ||
| + | |||
| + | To check whether both operands refer to the same object or not, you should use the '' | ||
| + | |||
| + | Run the code presented in the editor. | ||
| + | |||
| + | <code python> | ||
| + | a_string = [' | ||
| + | b_string = a_string | ||
| + | |||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | print() | ||
| + | |||
| + | a_string = [' | ||
| + | b_string = [' | ||
| + | |||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | |||
| + | The output is: | ||
| + | |||
| + | <code ; output> | ||
| + | a_string identity: 3687888 | ||
| + | b_string identity: 3687888 | ||
| + | The result of the value comparison: True | ||
| + | The result of the identity comparison: True | ||
| + | |||
| + | a_string identity: 3689048 | ||
| + | b_string identity: 9418632 | ||
| + | The result of the value comparison: True | ||
| + | The result of the identity comparison: False | ||
| + | </ | ||
| + | This could be depicted as follows: | ||
| + | {{ : | ||
| + | |||
| + | When you process the data, you’ll come to the point where you may want to have distinct copies of objects that you can modify without automatically modifying the original at the same time. | ||
| + | |||
| + | Let's have a look at the following code. Its intention is to: | ||
| + | * make a real, independent copy of '' | ||
| + | * modify the original object; | ||
| + | * see the contents of both objects. | ||
| + | |||
| + | Pay attention to the code presented in the right pane, of which '' | ||
| + | |||
| + | <code python> | ||
| + | print(" | ||
| + | print(" | ||
| + | a_list = [10, " | ||
| + | b_list = a_list[:] | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | |||
| + | print() | ||
| + | print(" | ||
| + | print(" | ||
| + | b_list[2][0] = 112 | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | |||
| + | </ | ||
| + | |||
| + | When you run the code, you get the following output: | ||
| + | <code ; output> | ||
| + | Part 1 | ||
| + | Let's make a copy | ||
| + | a_list contents: [10, ' | ||
| + | b_list contents: [10, ' | ||
| + | Is it the same object? False | ||
| + | |||
| + | Part 2 | ||
| + | Let's modify b_list[2] | ||
| + | a_list contents: [10, ' | ||
| + | b_list contents: [10, ' | ||
| + | Is it the same object? False | ||
| + | </ | ||
| + | So, despite the fact that '' | ||
| + | |||
| + | The explanation of the behavior presented on the previous page is: | ||
| + | |||
| + | * the '' | ||
| + | * we’ve run a **shallow copy** that constructs a new compound object, '' | ||
| + | * as you can see, a shallow copy is only one level deep. The copying process does not recurse and therefore does not create copies of the child objects, but instead populates '' | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | If you want to make an independent copy of a compound object (list, dictionary, custom class instance) you should make use of deep copy, which: | ||
| + | * constructs a new compound object and then, recursively, | ||
| + | * takes more time to complete, as there are many more operations to be performed; | ||
| + | * is implemented by the '' | ||
| + | {{ : | ||
| + | |||
| + | A code creating an independent copy of the '' | ||
| + | |||
| + | <code python> | ||
| + | import copy | ||
| + | |||
| + | print(" | ||
| + | a_list = [10, " | ||
| + | b_list = copy.deepcopy(a_list) | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | |||
| + | print() | ||
| + | print(" | ||
| + | b_list[2][0] = 112 | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | |||
| + | </ | ||
| + | |||
| + | <code ; output> | ||
| + | Let's make a deep copy | ||
| + | a_list contents: [10, ' | ||
| + | b_list contents: [10, ' | ||
| + | Is it the same object? False | ||
| + | |||
| + | Let's modify b_list[2] | ||
| + | a_list contents: [10, ' | ||
| + | b_list contents: [10, ' | ||
| + | Is it the same object? False | ||
| + | </ | ||
| + | |||
| + | The graphical representation should look like the following: | ||
| + | {{ : | ||
| + | |||
| + | The ' | ||
| + | |||
| + | But think about making use of polymorphism when you need a universal function to copy any type object, so that in that case using a '' | ||
| + | |||
| + | In the following example, we'll compare the performance of three ways of copying a large compound object (a million three-element tuples). | ||
| + | |||
| + | <code python> | ||
| + | import copy | ||
| + | import time | ||
| + | |||
| + | a_list = [(1,2,3) for x in range(1_000_000)] | ||
| + | |||
| + | print(' | ||
| + | time_start = time.time() | ||
| + | b_list = a_list | ||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | print() | ||
| + | |||
| + | print(' | ||
| + | time_start = time.time() | ||
| + | b_list = a_list[:] | ||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | print() | ||
| + | |||
| + | print(' | ||
| + | time_start = time.time() | ||
| + | b_list = copy.deepcopy(a_list) | ||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | |||
| + | The first approach is a simple reference copy. This is done very quickly, as there’s nearly nothing to be done by the CPU – just a copy of a reference to ' | ||
| + | |||
| + | The second approach is a shallow copy. This is slower than the previous code, as there are 1,000,000 references (not objects) created. | ||
| + | |||
| + | The third approach is a deep copy. This is the most comprehensive operation, as there are 3,000,000 objects created. | ||
| + | |||
| + | Test it locally on your computer. | ||
| + | |||
| + | /<code ; output> | ||
| + | Single reference copy | ||
| + | Execution time: 0.0 | ||
| + | Memory chunks: 140558259798656 140558259798656 | ||
| + | Same memory chunk? True | ||
| + | |||
| + | Shallow copy | ||
| + | Execution time: 0.005 | ||
| + | Memory chunks: 140558259798656 140558259799232 | ||
| + | Same memory chunk? False | ||
| + | |||
| + | Deep copy | ||
| + | Execution time: 1.994 | ||
| + | Memory chunks: 140558259798656 140558259802752 | ||
| + | Same memory chunk? False | ||
| + | </ | ||
| + | |||
| + | The same '' | ||
| + | |||
| + | The code on the right presents the code that safely copies the dictionary. | ||
| + | <code python> | ||
| + | import copy | ||
| + | |||
| + | a_dict = { | ||
| + | 'first name': ' | ||
| + | 'last name': ' | ||
| + | ' | ||
| + | } | ||
| + | b_dict = copy.deepcopy(a_dict) | ||
| + | print(' | ||
| + | print(' | ||
| + | print(" | ||
| + | a_dict[' | ||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | |||
| + | Run the code to get the following output: | ||
| + | <code ; output> | ||
| + | Memory chunks: 140113809094048 140113808387040 | ||
| + | Same memory chunk? False | ||
| + | Let's modify the movies list | ||
| + | a_dict movies: [' | ||
| + | b_dict movies: [' | ||
| + | </ | ||
| + | |||
| + | The code in the editor copies the dictionary in a safe manner. | ||
| + | |||
| + | <code python> | ||
| + | import copy | ||
| + | |||
| + | class Example: | ||
| + | def __init__(self): | ||
| + | self.properties = [" | ||
| + | print(" | ||
| + | |||
| + | a_example = Example() | ||
| + | b_example = copy.deepcopy(a_example) | ||
| + | print(" | ||
| + | print(" | ||
| + | print() | ||
| + | print(" | ||
| + | b_example.properties.append(" | ||
| + | print(' | ||
| + | print(' | ||
| + | </ | ||
| + | |||
| + | Run it to get the following output: | ||
| + | <code ; output> | ||
| + | Hello from __init__() | ||
| + | Memory chunks: 140319166493840 140319166494096 | ||
| + | Same memory chunk? False | ||
| + | |||
| + | Let's modify the movies list | ||
| + | a_example.properties: | ||
| + | b_example.properties: | ||
| + | </ | ||
| + | |||
| + | Pay attention to the fact that the '' | ||
| + | |||
| + | This method is not executed for the '' | ||
| + | |||
| + | == LAB | ||
| + | === Scenario | ||
| + | **Introduction** | ||
| + | Imagine you have been hired to help run a candy warehouse. | ||
| + | ** | ||
| + | The task** | ||
| + | * Your task is to write a code that will prepare a proposal of reduced prices for the candies whose total weight exceeds 300 units of weight (we don’t care whether those are kilograms or pounds) | ||
| + | * Your input is a list of dictionaries; | ||
| + | * Prepare a copy of the source list (this should be done with a one-liner) and then iterate over it to reduce the price of each delicacy by 20% if its weight exceeds the value of 300; | ||
| + | * Present an original list of candies and a list that contains the proposals; | ||
| + | * Check if your code works correctly when copying and modifying the candy item details. | ||
| + | |||
| + | **Expected output** | ||
| + | <code ; output> | ||
| + | Source list of candies | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | ****************** | ||
| + | Price proposal | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | </ | ||
| + | <code python> | ||
| + | warehouse = list() | ||
| + | warehouse.append({' | ||
| + | warehouse.append({' | ||
| + | warehouse.append({' | ||
| + | warehouse.append({' | ||
| + | warehouse.append({' | ||
| + | |||
| + | print(' | ||
| + | for item in warehouse: | ||
| + | print(item) | ||
| + | |||
| + | </ | ||
| + | | ||
| + | === resultat | ||
| + | <code python> | ||
| + | import copy | ||
| + | |||
| + | warehouse = list() | ||
| + | warehouse.append({' | ||
| + | warehouse.append({' | ||
| + | warehouse.append({' | ||
| + | warehouse.append({' | ||
| + | warehouse.append({' | ||
| + | |||
| + | print(' | ||
| + | for item in warehouse: | ||
| + | print(item) | ||
| + | |||
| + | warehouse_b = copy.deepcopy(warehouse) | ||
| + | |||
| + | print(' | ||
| + | for item in warehouse_b: | ||
| + | if item[' | ||
| + | item[' | ||
| + | for item in warehouse: | ||
| + | print(item) | ||
| + | |||
| + | warehouse[0][' | ||
| + | |||
| + | print(' | ||
| + | for item in warehouse: | ||
| + | print(item) | ||
| + | |||
| + | print(' | ||
| + | for item in warehouse_b: | ||
| + | print(item) | ||
| + | </ | ||
| + | <code ; output> | ||
| + | Source list of candies | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | Source list of candies discount | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | Source list of candies | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | Source list of candies discount | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | {' | ||
| + | </ | ||
| + | |||
| + | == LAB | ||
| + | === Scenario | ||
| + | The previous task was a very easy one. Now let's rework the code a bit: | ||
| + | |||
| + | * introduce the Delicacy class to represent a generic delicacy. The objects of this class will replace the old school dictionaries. Suggested attribute names: name, price, weight; | ||
| + | * your class should implement the '' | ||
| + | * experiment with the '' | ||
| + | |||
| + | === resultat | ||
| + | <code python> | ||
| + | import copy | ||
| + | |||
| + | class Delicacy(): | ||
| + | def __init__(self, | ||
| + | self.name = name | ||
| + | self.price = price | ||
| + | self.weight = weight | ||
| + | | ||
| + | def __str__(self): | ||
| + | return f" | ||
| + | | ||
| + | a = list() | ||
| + | |||
| + | d1 = Delicacy(" | ||
| + | a.append(d1) | ||
| + | a.append(Delicacy(" | ||
| + | d2 = copy.copy(d1) | ||
| + | a.append(d2) | ||
| + | d3 = copy.deepcopy(d1) | ||
| + | d3.name = " | ||
| + | a.append(d3) | ||
| + | |||
| + | for i in a: | ||
| + | print(i) | ||
| + | | ||
| + | d2.name=" | ||
| + | a.append(d2) | ||
| + | print() | ||
| + | |||
| + | for i in a: | ||
| + | print(i) | ||
| + | </ | ||
| + | <code ; output> | ||
| + | aaa - 10 - 100 | ||
| + | bbb - 20 - 200 | ||
| + | aaa - 10 - 100 | ||
| + | ccc - 10 - 100 | ||
| + | |||
| + | aaa - 10 - 100 | ||
| + | bbb - 20 - 200 | ||
| + | dddd - 10 - 100 | ||
| + | ccc - 10 - 100 | ||
| + | dddd - 10 - 100 | ||
| + | </ | ||
| + | == Section summary | ||
| + | Important things to remember: | ||
| + | |||
| + | * the '' | ||
| + | * a deep copy operation takes significantly more time than any shallow copy operation; | ||
| + | * the '' | ||
| + | * deep copy might cause problems when there are cyclic references in the structure to be copied. | ||