Applying Copying to User Defined Classes
00:00
This lesson and the next are going to be all about how the built-in copy module behaves when dealing with user-defined classes. One of the reasons the copy module is so versatile is that except for a handful of data types like files, network sockets, and modules, you can apply copy() and deepcopy() to pretty much anything, including classes you define.
00:20 In this lesson, you’ll examine their default behavior without writing any custom code for handling copying. We’re going to start by defining a class for representing rectangles.
00:30
Its instance attributes will be two objects of a point class, which you’ll also define that represent the x, y coordinates of the top left and bottom right corners of the rectangle. class Rectangle:
00:43
def __init__(self, top_left, bottom_right): and in the body of the function, self.top_left is set to the value of top_left, while self.bottom_right is set to the value of bottom_right.
00:59
Also define a __repr__ method that only takes the self parameter, which returns the f-string representation Rectangle( open parenthesis, the value of self. top_left, the value of self. bottom_right, close parenthesis. That’s the Rectangle class.
01:17
Now define the Point class. class Point: This class’s __init__ will take the parameters, self, x, and y. def __init__(self, x, y): self.x is set to x, and self.y is set to y.
01:34
Define a __repr__ method here as well. Again, only taking in the parameter self, return the f-string, Point(x= the value of self.x, y= the value of self.y). Close parenthesis.
01:50
Test out the classes by creating a variable bounding_box that is the result of calling Rectangle(, passing in two Point objects.
01:57
Point(10, 20) and Point(30, 40). Examine bounding_box. Cool! Based on the __repr__ methods you defined, you see that bounding_box is a Rectangle object defined by a top left Point with the coordinates 10, 20 and a bottom right Point with the coordinates 30, 40.
02:16
So how does this play with the copy module? import copy. Make a shallow copy.
02:24
shallow_copy = copy. copy(bounding_box) and a deep copy.
02:32
deep_copy = copy.deepcopy(bounding_box).
02:37
User-defined objects are mutable by default, so go ahead and modify bounding_box by setting its bottom_right attribute to a Point with the values 500, 700. bounding_box. bottom_right = Point(500, 700).
02:55
What do you think? Have either the shallow copy or the deep copy changed because of this? Examine bounding_box,
03:04
you’ll see the coordinates 10, 20, 500, 700. Examine shallow_copy
03:11
and the coordinates, 10, 20, 30, and 40 appear. And examine deep_copy, same as shallow copy, the coordinates 10, 20, 30, and 40. So far, no changes.
03:25
Did I trick you? Remember the expected behavior is that the top level object, bounding_box, is distinct from its copies, and that’s what we saw here.
03:34
But what happens if you modify one of its Point objects directly? bounding_box.top_left. x = 50. Now look at bounding_box.
03:47
It has the coordinates 50, 20, 500, 700. Check shallow_copy. It has the coordinates 50, 20, 30, 40, and check deep_copy.
04:01
The original coordinates remain 10, 20, 30, and 40. Shallow copy was mutated along with bounding_box, but deep copy remained distinct, and this is exactly how we would expect copy behavior to work for any composite object.
04:17
The shallow copy shares references while deep copies do not. With an object like this Rectangle class, default behavior is probably all you need.
04:26 In the next lesson, you’ll see a case where the default behavior isn’t enough and a custom solution is required.
Bartosz Zaczyński RP Team on Aug. 29, 2025
@Daniel Podobinski This should work as described in the video lesson regardless of your code editor. Did you mutate the bounding_box variable in-place or did you rebind it to a brand new value?
Daniel Podobinski on Aug. 29, 2025
@Bartosz Zaczynski What I noticed after posting was that, if I did exactly as shown in the video i.e:
bounding_box.top_left.x = 50
the shallow copy WAS affected. But out of curiosity, I also tried
bounding_box.bottom_right.x = 1000
and this did NOT change the shallow copy.
Bartosz Zaczyński RP Team on Aug. 29, 2025
@Daniel Podobinski Okay, so repeating the same steps exactly as shown in the video does actually produce the same results on your end 🙂
The reason why you observe a different behavior for the .bottom_right attribute is because you rebound it to a new value in an earlier step:
bounding_box.bottom_right = Point(500, 700)
This overwrites the original Point(30, 40), which is still referenced by shallow_copy. So, when you later mutate bounding_box.bottom_right.x, this doesn’t affect the original point. Instead, it modifies this new Point(500, 700) instance, which is only attached to bounding_box.
When you repeat the same steps but without the line of code listed above, then you’ll get the same result as for the .top_left attribute. To understand this better, it helps to draw the reference graph, or you can check the pitfalls of shallow copying.
Become a Member to join the conversation.

Daniel Podobinski on Aug. 29, 2025
The behaviour described at 4:15 is not what I got when running this in Spyder. Both shallow and deep copies were unaffected.