突变后更新依赖属性
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 中的每个其他实例变量彼此。
假设我有以下 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 中的每个其他实例变量彼此。