突变后更新依赖属性

Updating Dependent Attributes After Mutation

假设我有以下 classes:

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

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

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.

当 class 所依赖的属性之一发生变化时,更新 class 的属性的 pythonic 方法是什么?

选项 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())

(请注意,我在变量前使用 _ 表示它们是私有的,只能通过 getters 和 setters 访问。例如,变量length 不应由用户设置——只能通过 LineSegment class。)

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

选项 2:@属性 装饰器

添加获取和设置逻辑的更 Pythonic 方法是 @property decorator,正如@progmatico 在他们的评论中指出的那样,它调用 decorated getter 和 setter 访问实例变量时的方法。由于我们需要做的只是在需要时计算长度,所以我们可以暂时保留其他实例变量public:

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 中的每个其他实例变量彼此。