为什么我的move_rectangle函数会改变两个单独的Rectangle对象的角属性?

时间:2018-04-05 00:19:31

标签: python class

免责声明:这不是家庭作业或任何与学校作业相关的内容。这纯粹是我试图更好地理解如何使用类编程(以及制作类实例的浅/深副本)。

所以我写了两个课程:PointRectangle。在这里,我创建了两个Rectangle对象:boxnew_box

当我调用move_rectangle(rect, dx, dy)方法并运行我的代码(我将尝试在此处发布)时,它应该返回box的深层副本(其返回值)被分配到new_box)。

当我检查:box is new_box时,它会返回False(我的预期),但当我检查boxnew_box的x坐标时,< strong>两个的x值都发生了变化。

这就是我的预期:

box.corner.x = 3
new_box.corner.x = 6

相反,我得到了:

box.corner.x = 6    
new_box.corner.x = 6

如果框应该是new_box的单独对象,那该怎么办?

非常感谢任何帮助/提示/建议!

这是我的代码:

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)

1 个答案:

答案 0 :(得分:1)

copy.deepcopy函数只知道如何深度复制实现复制协议(或酸洗协议)的类型。列表和dicts等内置容器当然会实现它,但是Rectangle没有。

通常,“默认”行为才有效。但它不适合你,因为你实际上并没有使用Point的实例属性;相反,您依赖于提供默认值的class属性。这对于0.0等不可变值非常有用,但对Point()等可变值没有那么多。

以便copy.deepcopy(self)返回一个新的Rectangle 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

所以,你有两个选择:

  • 实施复制或pickle协议,如copy docs。
  • 中所述
  • 请勿使用deepcopy,只需手动执行。

第二个更简单:

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

虽然我们在这里,你在这里做一些奇怪的事情。

首先,将self参数命名为self以外的其他内容,如rect,违反了一个非常重要的习惯用法。任何熟悉Python的人都会进行双重操作,开始阅读代码,然后在解析代码所做的事情之前再做一次双重考虑。

其次,对于这样的类型,您通常希望能够将值传递给构造函数,如下所示:

class Point:
    def __init__(self, x=0.0, y=0.0):
        self.x, self.y = x, y

如果您按照这种方式设计,move_rectangle方法会变得更简单:

def move_rectangle(self, dx, dy):
    return Rectangle(Point(self.corner.x+dx, self.corner.y+dy))

最后,您有一个名为grow_rectangle的方法,该方法在就地增长self,而另一个名为move_rectangle的方法会单独留下self,而是返回一个不同的矩形。那令人困惑;没有明显的理由让任何人阅读这些名称,一个人应该改变,另一个人应该改变。

在Python 3.7中,使用新的dataclass功能可能更好。 3.7目前仍处于测试阶段,但是您可以使用第三方attrs库获得类似的功能,或者,如果您希望这些对象在构建后是不可变的,只需使用namedtuple