创建可管理的类属性

对实例属性的set或get进行额外的处理(例如,类型检查或验证)。

可以使用类property对属性进行set,get,delete的定制化。类签名如下:
class property(fget=None, fset=None, fdel=None, doc=None)

返回一个property的属性,fget是用于获取属性值的函数。 fset是用于设置属性值的功能。 fdel是用于删除属性值的函数。 doc为该属性创建一个docstring。
典型的用途是定义托管的属性x,如下:

class C:
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value
    
    def delx(self):
        del self._x

    x = property(getx, setx, delx, "I'm the 'x' property.")

如果c是C的实例,则c.x将调用getter,c.x = value将调用setter,而del c.x则是删除属性。

进一步的,可以使用property的装饰器,以下示例可以说明:

class Parrot:
    def __init__(self):
        self._voltage = 100000

    @property
    def voltage(self):
    """Get the current voltage."""
        return self._voltage

@property装饰器将voltage()方法转换为具有相同名称的只读属性的“getter”,并将voltage的文档字符串设置为“获取当前voltage”。
property对象具有可用作装饰器的getter,setter和deleter方法,这些方法创建属性的副本,并将相应的访问器函数设置为装饰函数。 最好用一个例子解释一下:

class Person:
    def __init__(self, first_name):
        self.first_name = first_name

    # Getter function
    @property
    def first_name(self):
        return self._first_name

    # Setter function
    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    @Deleter function (optional)
    @first_name.deleter
    def first_name(self):
        raise AttributeError("Can't delete attribute")

在前面的代码中,有三个相关的方法,所有这些方法都必须具有相同的名称。 第一种方法是getter函数,并将first_name建立为property。 另外两个方法将可选的setter和deleter函数附加到first_name的property。 需要强调的是,除非已使用@property将first_name设置为property,否则将不会定义@ first_name.setter和@ first_name.deleter装饰器。

property的关键特征是它看起来像普通属性,但是访问会自动触发getter,setter和deleter方法。

>>> a  = Person('Guido')
>>> a.first_name    # Calls the getter
'Guido'
>>> a.first_name = 42   # Calls the setter
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-69-6341bdece797> in <module>
----> 1 a.first_name = 42

<ipython-input-64-2b97c0306ce4> in first_name(self, value)
      8     def first_name(self, value):
      9         if not isinstance(value, str):
---> 10             raise TypeError('Expected a string')
     11         self._first_name = value
     12     @first_name.deleter

TypeError: Expected a string

实现property时,底层数据(如果有)仍需要存储在某个地方。 因此,在get和set方法中,可以看到对_first_name属性的直接操作,这是实际数据所在的位置。 另外,可能会问为什么__init __()方法设置self.first_name而不是self._first_name。 在此示例中,属性的全部要点是在设置属性时应用类型检查。 因此,可能还希望在初始化期间进行此类检查。 通过设置self.first_name,设置操作将使用setter方法(而不是通过访问self._first_name来绕过它)。

property属性实际上是捆绑在一起的方法的集合。 如果检查带有property的类,则可以在property本身的fget,fset和fdel属性中找到原始方法。

>>> Person.first_name.fget
<function Person.first_name at 0x1006a60e0> 
>>> Person.first_name.fset
<function Person.first_name at 0x1006a6170> 
>>> Person.first_name.fdel
<function Person.first_name at 0x1006a62e0> 
>>>

仅在确实需要对属性访问执行额外处理的情况下,才应使用property。

不要编写实际上不会添加任何额外内容的property。 一方面,它使代码更加冗长和混乱。 其次,这会使程序运行慢很多。 最后,它没有真正的设计优势。 具体来说,如果以后决定需要对普通属性添加额外的处理,则可以在不更改现有代码的情况下将其提升为property。 这是因为访问该属性的代码的语法将保持不变。

property也可以是定义计算属性的一种方式。 这些属性不是实际存储的,而是按需计算的。

import math 
class Circle:
    def __init__(self, radius): 
        self.radius = radius
    @property
    def area(self):
        return math.pi * self.radius ** 2
    @property
    def perimeter(self):
        return 2 * math.pi * self.radius

>>> c = Circle(4.0)
>>> c.radius 4.0
>>> c.area    # Notice lack of ()
50.26548245743669 
>>> c.perimeter   # Notice lack of ()
25.132741228718345 
>>>

在这里,property的使用形成一个非常统一的实例接口,因为半径,面积和周长都作为简单属性来访问,而不是简单属性和方法调用的混合。

最后,且重要的是:不要编写具有很多重复属性定义的Python代码。代码重复会导致代码膨胀、出错和丑陋。事实证明,使用描述符(descriptor)或闭包(closure)可以更好地实现相同的功能。

posted @ 2019-12-08 16:43  Jeffrey_Yang  阅读(175)  评论(0编辑  收藏  举报