Python中有一个被称为属性函数(property)的小概念,它可以做一些有用的事情。在这篇文章中,我们将看到如何能做以下几点:
- 将类方法转换为只读属性
- 重新实现一个属性的setter和getter方法
class Person(object):
""""""
# ----------------------------------------------------------------------
def __init__(self, first_name, last_name):
"""Constructor"""
self.first_name = first_name
self.last_name = last_name
# ----------------------------------------------------------------------
@property
def full_name(self):
"""
Return the full name
"""
return "%s %s" % (self.first_name, self.last_name)
person = Person("Mike", "Driscoll")
print(person.full_name)
print(person.first_name)
#Mike Driscoll
#Mike
# person.full_name = "Jackalope" # AttributeError: can't set attribute
正如你所看到的,因为我们将方法变成了属性,我们可以使用正常的点符号访问它。但是,如果我们试图将该属性设为其他值,我们会引发一个AttributeError错误。改变full_name属性的唯一方法是间接这样做:
person.first_name = "Dan"
print(person.full_name) #Dan Driscoll
使用Python property取代setter和getter方法
from decimal import Decimal
########################################################################
class Fees(object):
""""""
# ----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self._fee = None
# ----------------------------------------------------------------------
def get_fee(self):
"""
Return the current fee
"""
return self._fee
# ----------------------------------------------------------------------
def set_fee(self, value):
"""
Set the fee
"""
if isinstance(value, str):
self._fee = Decimal(value)
elif isinstance(value, Decimal):
self._fee = value
f = Fees()
f.set_fee("1")
print(f.get_fee()) # 1
如果你想添加可以使用正常点符号访问的属性,而不破坏所有依赖于这段代码的应用程序,你可以通过添加一个属性函数非常简单地改变它:
from decimal import Decimal
########################################################################
class Fees(object):
""""""
# ----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self._fee = None
# ----------------------------------------------------------------------
def get_fee(self):
"""
Return the current fee
"""
return self._fee
# ----------------------------------------------------------------------
def set_fee(self, value):
"""
Set the fee
"""
if isinstance(value, str):
self._fee = Decimal(value)
elif isinstance(value, Decimal):
self._fee = value
fee = property(get_fee, set_fee)
f = Fees()
f.set_fee("1")
print(f.fee) # 1
f.fee = "2"
print(f.get_fee()) # 2
正如你所看到的,当我们以这种方式使用属性函数时,它允许fee属性设置并获取值本身而不破坏原有代码。让我们使用属性装饰器来重写这段代码,看看我们是否能得到一个允许设置的属性值。
把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值
from decimal import Decimal
########################################################################
class Fees(object):
""""""
# ----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self._fee = None
# ----------------------------------------------------------------------
@property
def fee(self):
"""
The fee property - the getter
"""
return self._fee
# ----------------------------------------------------------------------
@fee.setter
def fee(self, value):
"""
The setter of the fee property
"""
if isinstance(value, str):
self._fee = Decimal(value)
elif isinstance(value, Decimal):
self._fee = value
# ----------------------------------------------------------------------
if __name__ == "__main__":
f = Fees()
f.fee = '10'
print(f.fee) # 10
如果你看属性函数的说明,它有fget, fset, fdel和doc几个参数。如果你想对属性使用del命令,你可以使用@fee.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")
if __name__ == '__main__':
a = Person('Guido')
print(vars(a))
# print(a.first_name) # Guido
# a.first_name = 42 # Calls the setter
# outputs: TypeError: Expected a string
在实现一个property的时候,底层数据(如果有的话)仍然需要存储在某个地方。 因此,在get和set方法中,你会看到对 _first_name 属性的操作,这也是实际数据保存的地方。 另外,你可能还会问为什么 init() 方法中设置了 self.first_name 而不是 self._first_name 。 在这个例子中,我们创建一个property的目的就是在设置attribute的时候进行检查。 因此,你可能想在初始化的时候也进行这种类型检查。通过设置 self.first_name ,自动调用 setter 方法, 这个方法里面会进行参数的检查,否则就是直接访问 self._first_name 了。