变异后更新依赖属性

时间:2021-05-02 00:17:16

标签: python class object parameters attributes

假设我有以下课程:

import math


class LineSegment:
    def __init__(
        self,
        origin,
        termination,
    ):
        self.origin = origin
        self.termination = termination
        self.length = self.calculate_length()

    def calculate_length(self):
        return math.sqrt(
            (self.origin.x - self.termination.x) ** 2
            + (self.origin.y - self.termination.y) ** 2
        )


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

LineSegment 类的一个对象由 Point 类的两个对象组成。现在,假设我这样初始化一个对象:

this_origin = Point(x=0, y=0)
this_termination = Point(x=1, y=1)
this_line_segment = LineSegment(origin=this_origin, termination=this_termination)

注意:线段的初始化会自动计算其长度。这对代码库的其他部分至关重要,并且无法更改。我可以看到它的长度是这样的:

print(this_line_segment.length)    # This prints "1.4142135623730951" to the console.

现在,我需要改变 this_line_segment 子对象的一个​​参数:

this_line_segment.origin.x = 1

但是,this_line_segments 长度属性不会根据新原点的 x 坐标更新:

print(this_line_segment.length)    # This still prints "1.4142135623730951" to the console.

当它们依赖的属性之一发生变化时,实现更新类属性的pythonic方法是什么?

1 个答案:

答案 0 :(得分:1)

选项 1:Getter 和 Setter 方法

在其他面向对象的编程语言中,您想要的行为,在访问实例变量的值时添加额外的逻辑,通常由对象中所有实例变量的“getter”和“setter”方法实现:

class LineSegment:
    def __init__(
        self,
        origin,
        termination,
    ):
        self._origin = origin
        self._termination = termination

    # getter method for origin
    def get_origin(self):
        return self._origin

    # setter method for origin
    def set_origin(self,new_origin):
        self._origin = new_origin

    # getter method for termination
    def get_termination(self):
        return self._termination

    # setter method for termination
    def set_termination(self,new_termination):
        self._termination = new_termination

    def get_length(self):
        return math.sqrt(
            (self.get_origin().x - self.get_termination().x) ** 2
            + (self.get_origin().y - self.get_termination().y) ** 2
        ) #Calls the getters here, rather than the instance vars in case
          # getter logic is added in the future

这样每次您get() length 变量时都会执行额外的长度计算,而不是this_line_segment.origin.x = 1,您这样做:

new_origin = this_line_segment.get_origin()
new_origin.x = 1
this_line_segment.set_origin(new_origin)
print(this_line_segment.get_length())

(注意,我在变量前使用 _ 表示它们是私有的,只能通过 getter 和 setter 访问。例如,变量 length 永远不应由用户设置-- 仅通过 LineSegment 类。)

然而,显式的 getter 和 setter 显然是一种在 Python 中管理变量的笨拙方式,在 Python 中,宽松的访问保护使得直接访问它们更加透明。

选项 2:@property 装饰器

添加获取和设置逻辑的更 Pythonic 方法是 @property decorator,正如@progmatico 在他们的评论中指出的那样,它在访问实例变量时调用修饰的 getter 和 setter 方法。由于我们需要做的就是在需要时计算长度,因此我们现在可以将其他实例变量公开:

class LineSegment:
    def __init__(
        self,
        origin,
        termination,
    ):
        self.origin = origin
        self.termination = termination
    
    # getter method for length
    @property
    def length(self):
        return math.sqrt(
            (self.origin.x - self.termination.x) ** 2
            + (self.origin.y - self.termination.y) ** 2
        )

和用法:

this_line_segment = LineSegment(origin=Point(x=0,y=0), 
                                termination=Point(x=1,y=1))
print(this_line_segment.length) # Prints 1.4142135623730951

this_line_segment.origin.x = 1
print(this_line_segment.length) # Prints 1.0

在 Python 3.7.7 中测试。

注意:我们必须在 length getter 中进行长度计算,而不是在 LineSegment 初始化时进行。我们不能在原点和终止实例变量的 setter 方法中进行长度计算,因此也不能在初始化中进行长度计算,因为 Point 对象是可变的,并且改变它不会调用 LineSegment 的 setter方法。虽然我们可以在选项 1 中做到这一点,但它会导致一种反模式,在这种情况下,在实例变量相互依赖的情况下,我们必须为对象的每个实例变量重新计算 setter 中的每个其他实例变量。