Reputation: 43
Disclaimer: This is not homework or anything related to a school assignment. This is purely me trying to better understand how programming with classes (and making shallow/ deep copies of class instances) works.
So I wrote two classes: Point
and Rectangle
. Here I'm creating two Rectangle objects: box
and new_box
.
When I invoke the move_rectangle(rect, dx, dy)
method and run my code (which I will attempt to post here), it's supposed to return a deep copy of box
(the returned value of which is assigned to new_box
).
When I check if: box is new_box
, it returns False
(which I expected), but when I check the x-coordinates of both box
and new_box
, both of their x-values were changed.
This is what I expected:
box.corner.x = 3
new_box.corner.x = 6
Instead, I got:
box.corner.x = 6
new_box.corner.x = 6
How can that be if box is supposed to be a separate object from new_box
?
Any help/ tips/ advice is greatly appreciated!
Here's my code:
import math
import copy
class Point (object):
""" Represent a point in a 2-D space """
x = 0.0
y = 0.0
def distance(p1, p2):
return math.sqrt((p2.x-p1.x)**2 + (p2.y-p1.y)**2)
def print_point(p):
print('(%g, %g)' % (p.x, p.y))
class Rectangle(object):
"""represent a rectangle.
attributes: width, height, corner."""
width = 0.0
height = 0.0
corner = Point()
def grow_rectangle(rect, dwidth, dheight):
rect.width += dwidth
rect.height += dheight
def move_rectangle(rect, dx, dy):
new_rect = copy.deepcopy(rect)
new_rect.corner.x += dx
new_rect.corner.y += dy
return new_rect
def findCenter(box):
p = Point()
p.x = box.corner.x + box.width/2.0
p.y = box.corner.y + box.height/2.0
return p
box = Rectangle()
box.corner.x = 3
box.corner.y = 8
new_box = Rectangle.move_rectangle(box, 3, 5)
print(new_box is box.corner)
print(box.corner.x)
print(new_box.corner.x)
Upvotes: 1
Views: 154
Reputation: 365807
The copy.deepcopy
function only knows how to deep-copy types that implement the copying protocol (or the pickling protocol). Built-in containers like lists and dicts of course implement it, but your Rectangle
does not.
Often, the "default" behavior just works. But it won't work for you, because you're not actually using an instance attribute for Point
; instead, you're relying on the class attribute providing a default value. Which works great for immutable values like 0.0
, but not so much for mutable values like Point()
.
So that copy.deepcopy(self)
returns a new Rectangle
with the same corner
:
>>> box1 = Rectangle
>>> box1
<__main__.Rectangle at 0x155c9e5c0>
>>> box1.corner
<__main__.Point at 0x155c9e5f8>
>>> box2 = copy.deepcopy(box1)
>>> box2
<__main__.Rectangle at 0x155c670f0>
>>> box2.corner
<__main__.Point at 0x155c9e5f8>
>>> box2 is box1
False
>>> box2.corner is box1.corner
True
So, you have two choices:
copy
docs.deepcopy
, just do it manually.The second one is simpler:
def move_rectangle(rect, dx, dy):
new_rect = Rectangle()
new_rect.corner = Point()
new_rect.corner.x = rect.corner.x + dx
new_rect.corner.y = rect.corner.y + dy
return new_rect
While we're at it, you're doing some odd things with classes here.
First, naming the self
parameter something other than self
, like rect
, violates a pretty major idiom. Anyone familiar with Python will do a double-take, start reading the code, and then do another double-take before they even get to parsing out what your code does.
Second, for types like this, you usually want to be able to pass values in to the constructor, like this:
class Point:
def __init__(self, x=0.0, y=0.0):
self.x, self.y = x, y
If you design things that way, your move_rectangle
method becomes a lot simpler:
def move_rectangle(self, dx, dy):
return Rectangle(Point(self.corner.x+dx, self.corner.y+dy))
Finally, you have a method named grow_rectangle
that grows self
in-place, but another one named move_rectangle
that leaves self
alone and instead returns a different rectangle. That's confusing; there's no obvious reason to anyone reading those names that one should mutate and the other copy.
In Python 3.7, it might be even nicer to use the new dataclass
feature. 3.7 is still in beta right now, but you can get similar functionality with the third-party attrs
library, or, if you want these objects to be immutable after construction, by just using namedtuple
.
Upvotes: 1