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.