假设我有以下课程:
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方法是什么?
答案 0 :(得分:1)
在其他面向对象的编程语言中,您想要的行为,在访问实例变量的值时添加额外的逻辑,通常由对象中所有实例变量的“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 中,宽松的访问保护使得直接访问它们更加透明。
添加获取和设置逻辑的更 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 中的每个其他实例变量。