问题
给某个实例attribute,增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证。
解决方案
自定义某个属性的一种简单方法是将它定义为一个property
。 例如,下面的代码定义了一个property,增加对一个属性简单的类型检查:
class Person(object):
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('Exception 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
成为一个属性。 其他两个方法给 first_name
属性添加了 setter
函数 和 deleter
函数。 需要强调的是只有在 first_name
属性被创建后, 后面的两个装饰器 @first_name.setter
和 @first_name.deleter
才能被定义。
property
的一个关键特征是,看上去跟普通的attribute没什么区别, 但是访问它的时候,会自动触发 getter 、setter 和 deleter 方法。例如:
a = Person('Xingzhe')
print(a)
<__main__.Person object at 0x1085d93c8>
print(a.first_name)
Xingzhe
a.first_name = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "prop.py", line 14, in first_name
raise TypeError('Expected a string')
TypeError: Expected a string
del a.first_name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can`t delete attribute
在实现一个property
的时候,底层数据(如果有的话)仍然需要存储在某个地方。 因此,在get
和set
方法中,会有对 _first_name
属性的操作,这也是实际数据保存的地方。
另外, __init__()
方法中设置了 self.first_name
而不是 self._first_name
,因为本示例中创建property
的目的就是在设置attribute的时候进行检查。通过设置 self.first_name
,会自动调用 setter
方法,进行参数的检查。
讨论
一个property属性,其实就是(绑定)一系列相关方法的集合。查看拥有property的类, 会发现property本身的fget、fset和fdel属性就是类里面的普通方法。比如:
print(Person.first_name.fget)
print(Person.first_name.fset)
print(Person.first_name.fdel)
<function Person.first_name at 0x1021f8510>
<function Person.first_name at 0x1021f8598>
<function Person.first_name at 0x1021f8620>
通常来讲,不需要直接取调用fget或者fset,它们会在访问property的时候自动被触发。
只有当确实需要对attribute执行其他额外的逻辑操作时,才应该使用到property。
property还是一种定义动态计算attribute的方法。 这种类型的attributes并不会被实际的存储,而是在需要的时候才进行计算。比如:
import math
class Circle(object):
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return math.pi * self.radius ** 2
@property
def diameter(self):
return self.radius * 2
@property
def permeter(self):
return 2 * math.pi * self.radius
通过使用多个property,将所有的访问接口形式统一起来,可以实现优雅的编程接口。对半径、直径、周长和面积的访问都是通过属性访问,就跟访问普通的attribute一样。 如果不这样做的话,就需要在代码中混合使用普通属性访问和方法调用。 下面是使用的实例:
c = Circle(4.0)
print(c.radius)
4.0
print(c.area)
50.26548245743669
print(c.diameter)
8.0
print(c.permeter)
25.132741228718345
网友评论