设置属性的属性

时间:2015-01-08 03:14:12

标签: python properties

我试图找出一种方法,让属性的setter触发器在顶级类上触发一些动作。

作为一个虚拟示例,我们可以说我的顶级课程是Segment。如果我将其端点的坐标直接存储为此对象x0y0x1y1的属性,并且每个端点的设置器触发了选定的行动。

但是,如果我想将它们分为两个Point成员作为属性p0p1,每个成员都具有属性xy,只要其中一个这些坐标被修改,没有明显的方法告诉Segment做某事。这是我希望能够做到的:

>>> segment = Segment(Point(0, 0), Point(3, 3))
>>> segment.p0
Point(0, 0)
>>> segment.p0.x
0
>>> segment.p1.y = 4
Length of segment changed to 5.0!  # This can only be printed by segment, not p1!

问题是,行segment.p1.y = 4首先在p1实例上调用segment的getter,然后在前一次调用返回时调用y的setter ,此时没有简单的方法让segment实例知道已经进行了更改。

我现在能想到的最好的事情就是以下几点:

class Point(object):
    def __init__(self, x, y, parent=None, name=None):
        self.parent, self.name = parent, name
        self._x, self._y = x, y

    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
        if parent is not None:
            setattr(self.parent, self.name, self)

    # Similar code for y goes here...


class Segment(object):
    def __init__(self, p0, p1):
        self.p0, self.p1 = p0, p1

    @property
    def p0(self):
        return self._p0
    @p0.setter
    def p0(self, point):
        self._p0 = point
        self.p0.parent = self
        self.p0.name = 'p0'
        if not self._silent:
            self.do_something()  # This would print the length in the above example

    # Similar code for p1 goes here...

虽然这可以达到我想要的目的,但我不太喜欢将该链接手动添加回父级,也不必如何制作Point的大量冗余副本如果做了类似的事情,那么对象或冒险有趣的错误:

p0, p1, p2 = Point(0, 0), Point(1, 1), Point(2, 2)
seg0 = Segment(p0, p1)
seg1 = Segment(p0, p2)
# The following line changes the value on both seg0 and seg1, but triggers
# the do_something call on seg1 only!
seg0.p0.x = 6

这是否有一些现成的食谱?任何人都可以想出一个更好的方法吗?

1 个答案:

答案 0 :(得分:2)

也许您正在寻找Observer design pattern

import math

class Point(object):
    def __init__(self, x, y, name=None):
        self.name = name
        self._x, self._y = x, y
        self.observers = []

    def observe(self, observer):
        self.observers.append(observer)

    def __repr__(self):
        return 'Point({}, {})'.format(self.x, self.y)

    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
        for o in self.observers:
            o.notify()

    @property
    def y(self):
        return self._y
    @y.setter
    def y(self, value):
        self._y = value
        for o in self.observers:
            o.notify()

class Segment(object):
    def __init__(self, p0, p1):
        self._p0, self._p1 = p0, p1
        p0.observe(self)
        p1.observe(self)

    def __repr__(self):
        return 'Segment({}, {})'.format(self.p0, self.p1)

    def notify(self):
        print('Length of {} changed to {}'.format(self, self.length()))

    def length(self):
        return math.sqrt((self.p0.x - self.p1.x)**2
                         + (self.p0.y - self.p1.y)**2)

    @property
    def p0(self):
        return self._p0
    @p0.setter
    def p0(self, point):
        self._p0 = point

    @property
    def p1(self):
        return self._p1
    @p1.setter
    def p1(self, point):
        self._p1 = point

segment = Segment(Point(0, 0), Point(3, 3))
print(segment.p0)
# Point(0, 0)
print(segment.p0.x)
# 0
segment.p1.y = 4

产量

Length of Segment(Point(0, 0), Point(3, 4)) changed to 5.0

p0, p1, p2 = Point(0, 0), Point(1, 1), Point(2, 2)
seg0 = Segment(p0, p1)
seg1 = Segment(p0, p2)
seg0.p0.x = 6

产量

Length of Segment(Point(6, 0), Point(1, 1)) changed to 5.09901951359
Length of Segment(Point(6, 0), Point(2, 2)) changed to 4.472135955