Python高级用法:property

2024-01-09 17:27:21

前言

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参数就是fgetfset,而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的最佳语法是使用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,而不能只修改一部分。

文章来源:https://blog.csdn.net/DuLNode/article/details/135472622
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。