创建可管理的类属性
对实例属性的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)可以更好地实现相同的功能。