property 实际上是一种实现了 __get__() 、 __set__() 方法的类,用户也可以根据自己的需要定义个性化的 property,其实质是一种特殊的数据描述符(数据描述符:如果一个对象同时定义了 __get__() 和 __set__() 方法,则称为数据描述符,如果仅定义了__get__() 方法,则称为非数据描述符)。它和普通描述符的区别在于:普通描述符提供的是一种较为低级的控制属性访问的机制,而 property 是它的高级应用,它以标准库的形式提供描述符的实现,其签名形式为:
property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
property 有两种常用的形式:
1、第一种形式
class Some_Class(object):
def __init__(self):
self._somevalue = 0
def get_value(self):
print('calling get method to return value')
return self._somevalue
def set_value(self, value):
print('calling set method to set value')
self._somevalue = value
def def_attr(self):
print('calling delete method to delete value')
def self._somevalue
x = property(get_value, set_value, del_attr, "I'm the 'x' property.")
obj = Some_Class()
obj.x = 10
print(obj.x + 2)
del obj.x
obj.x
2、第二种形式
class Some_Class(self):
_x = None
def __init__(self):
self._x = None
@property
def x(self):
print('calling get method to return value')
return self._x
@x.setter
def x(self, value):
print('calling set method to set value')
self._x = value
@x.deleter
def x(self):
print('calling delete method to delete value')
del self._x
以上我们可以总结出 property 的优势:
1、代码更简洁,可读性更强
2、更好的管理属性的访问。property 将对属性的访问直接转换为对对应的 get、set 等相关函数的调用,属性能够更好地被控制和管理,常见的应用场景如设置校验(如检查电子邮件地址是否合法)、检查赋值的范围(某个变量的赋值范围必须在 0 到 10 之间)以及对某个属性进行二次计算之后再返回给用户(将 RGB 形式表示的颜色转换为#******)或者计算某个依赖于其他属性的属性。
class Date(object):
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def get_date(self):
return self.year + '-' + self.month + '-' + self.day
def set_date(self, date_as_string):
year, month, day = date_as_string.split('-')
if not (2000 <= year <= 2017 and 0 <= month <= 12 and 0 <= day <= 31):
print('year should be in [2000:2017]')
print('month should be in [0:12]')
print('day should be in [0, 31]')
raise AssertionError
self.year = year
self.month = month
self.day = day
date = property(get_date, set_date)
创建一个 property 实际上就是将其属性的访问与特定的函数关联起来,相对于标准属性的访问,property 的作用相当于一个分发器,对某个属性的访问并不直接操作具体的对象,而对标准属性的访问没有中间这一层,直接访问存储属性的对象:
3、代码可维护性更好。property 对属性进行再包装,以类似于接口的形式呈现给用户,以统一的语法来访问属性,当具体实现需要改变的时候,访问的方式仍然可以保持一致。
4、控制属性访问权限,提高数据安全性。如果用户想设置某个属性为只读,来看看 property 是如何实现的。
class PropertyTest(object):
def __init__(self):
self.__var1 = 20
@property
def x(self):
return self.__var1
pt = PropertyTest()
print(pt.x)
pt.x = 12
注意这样使用 property 并不能真正意义达到属性只读的目的,正如以双下划线命令的变量并不是真正的私有变量一样,我们还是可以通过pt._PropertyTest__var1 = 30来修改属性。稍后我们会讨论如何实现真正意义上的只读和私有变量。
既然 property 本质是特殊类,那么就可以被继承,我们就可以自定义 property:
def update_meta(self, other):
self.__name__ = other.__name__
self.__doc__ = other.__doc__
self.__dict__.update(other.__dict__)
return self
class UserProperty(property):
def __new__(cls, fget=None, fset=None, fdel=None, doc=None):
if fget is not None:
def __get__(obj, objtype=None, name=fget.__name__):
fegt = getattr(obj, name)
return fget()
fget = update_meta(__get__, fget)
if fset is not None:
def __set__(obj, value, name=fset.__name__):
fset = getattr(obj, name)
return fset(value)
fset = update_meta(__set__, fset)
if fdel is not None:
def __delete__(obj, name=fdel.__name__):
fdel = getattr(obj, name)
return fdel()
fdel = update_meta(__delete__, fdel)
return property(fget, fset, fdel, doc)
class C(object):
def get(self):
return self._x
def set(self, x):
self._x = x
def delete(self):
del self._x
x = UserProperty(get, set, delete)
c = C()
c.x = 1
print(c.x)
def c.x
UserProperty 继承自 property,其构造函数 __new__(cls, fget=None, fset=None, fdel=None, doc=None) 中重新定义了 fget() 、 fset() 以及 fdel() 方法以满足用户特定的需要,最后返回的对象实际还是 property 的实例,因此用户能够像使用 property 一样使用 UserProperty。
使用 property 并不能真正完全达到属性只读的目的,用户仍然可以绕过阻碍来修改变量。我们来看看一个可行的实现:
def ro_property(obj, name, value):
setattr(obj.__class__, name, property(lambda obj: obj.__dict__["__" + name]))
setattr(obj, "__" + name, value)
class ROClass(object):
def __init__(self, name, available):
ro_property(self, "name", name)
self.available = available
a = ROClass("read only", True)
print(a.name)
a._Article__name = "modify"
print(a.__dict__)
print(ROClass.__dict__)
print(a.name)