property提供了一个内置的描述符类型,其作用是将一个属性链接到一组方法上(也就是将一个类方法作为一个类属性来用)。
property接受4个可选参数:fget、fset、fdel和doc(这四个参数可以不同时传入)。最后一个参数可以用来定义一个链接到属性的docstring(docstring是对链接到方法的属性的描述)。
下面我们来构建一个Rectangle类,其控制方法有两种,一种是直接访问保存两个顶点的属性,另一种是调用width和height。
class Rectangle:
def __init__(self, x1, y1, x2, y2):
self.x1, self.y1 = x1, y1
self.x2, self.y2 = x2, y2
def _width_get(self):
return self.x2 - self.x1
def _width_set(self, value):
self.x2 = self.x1 + value
def _height_get(self):
return self.y2 - self.y1
def _height_set(self, value):
self.y2 = self.y1 + value
width = property(
_width_get, _width_set,
doc="rectangle width measured from left"
)
height = property(
_height_get, _height_set,
doc="rectangle height measured from top"
)
def __repr__(self):
return "{}({}, {}, {}, {})".format(
self.__class__.__name__,
self.x1, self.y1, self.x2, self.y2
)
首先我们要知道一个前提:根据对角线上的两个顶点可以确定一个矩形,所以在初始化时传入两个顶点的坐标。
_width_get
的作用是根据顶点来获取矩形的宽,_width_set
是根据第一个顶点来修改第二个顶点的值,来达到修改矩形宽度的作用。_height_get
和_height_set
和上面的用法一致。
随后我们使用property来创建两个属性width和height,以width的创建为例, _width_get
, _width_set
对应的property参数就是fget
和fset
,而doc中的参数则是对属性的说明。
当定义好这个属性之后,每当访问到width
/height
时会调用_width_get
/_height_get
获取长度,当为width/height赋值时会调用_width_set
/_height_set
来设置第二个顶点的值。
在类中还存在一个__repr__
方法,是当使用Rectangle类对象时用来显示实例化对象信息的。
rectangle = Rectangle(10, 10, 25, 34)
print(rectangle.width, rectangle.height)
rectangle.width = 100
print(rectangle)
rectangle.height = 100
print(rectangle)
运行结果如下
15 24
Rectangle(10, 10, 110, 34)
Rectangle(10, 10, 110, 110)
我们可以看到当访问width和height时,会自动进行计算,当对width和height进行赋值的时候第一个点的坐标不变,根据所赋的值修改第二个点
我们使用help
来观察一下这个类
help(Rectangle)
运行结果如下:
Help on class Rectangle in module __main__:
class Rectangle(builtins.object)
| Rectangle(x1, y1, x2, y2)
|
| Methods defined here:
|
| __init__(self, x1, y1, x2, y2)
| Initialize self. See help(type(self)) for accurate signature.
|
| __repr__(self)
| Return repr(self).
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| height
| rectangle height measured from top
|
| width
| rectangle width measured from left
我们可以看到width和height以及针对他们的描述
以上的做法是有风险的,虽然property简化了描述符的编写,但是所创建的属性是利用当前类的方法实时创建,在使用类的继承时不会使用子类中覆写的方法。
比如以下的例子
class MetricRectangle(Rectangle):
def _width_get(self):
return "{} meters".format(self.x2 - self.x1)
print(MetricRectangle(0, 0, 100, 100).width)
在这里我们得到的运行结果仍然是100
,而不是我们期待的修改后的结果,要实现修改我们需要重新覆写整个property
class MetricRectangle(Rectangle):
def _width_get(self):
return "{} meters".format(self.x2 - self.x1)
width = property(_width_get, Rectangle.width.fset)
print(MetricRectangle(0, 0, 100, 100).width)
创建property的最佳语法是使用property作为装饰器。这会减少类内部方法的数量,并提高代码的可读性和可维护性。
class Rectangle:
def __init__(self, x1, y1, x2, y2):
self.x1, self.y1 = x1, y1
self.x2, self.y2 = x2, y2
@property
def width(self):
"""rectangle height measured from top"""
return self.x2 - self.x1
@width.setter
def width(self, value):
self.x2 = self.x1 + value
@property
def height(self):
"""rectangle height measured from top"""
return self.y2 - self.y1
@height.setter
def height(self, value):
self.y2 = self.y1 + value
不过这样的问题就在于在类的继承中,需要修改时只能复写整个property,而不能只修改一部分。