property
是用来实现属性可管理性的 built-in
数据类型(注意:很多地方将 property
称为函数,
个人认为这是不恰当的,它实际上是一种实现了 __get__()
、 __set__()
方法的类,
用户也可以根据自己的需要定义个性化的 property
),其实质是一种特殊的数据描述符
(数据描述符:如果一个对象同时定义了 __get__()
和 __set__()
方法,则称为数据描述符,
如果仅定义了 __get__()
方法,则称为非数据描述符)。它和普通描述符的区别在于:
普通描述符提供的是一种较为低级的控制属性访问的机制,
而 property
是它的高级应用,它以标注库的形式提供描述符的实现,其签名形式为:
property(fget=None,fset=None,fdel=None,doc=None) -> property attribute
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 del_attr(self):
print ("calling delete method to delete value")
del 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)
calling set method to set value calling get method to return value 12
del obj.x
calling delete method to delete value
# obj.x
2)第二种形式如下:
class Some_Class(object):
_x =None
def __init__(self):
self._x = name
@property
def x(self):
print("calling get method to return value")
@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
的优势
在了解完 property
的基本知识之后来探讨一下这些问题: property
到底有什么优势呢?
为什么要有这个特性呢? property
的优势可以简单地概括为以下儿点:
1)代码更简洁,可读性更强。这条优势是显而易见的.
显然 obj.x+=1
和obj.set_value(obj.get_value()+1)
要更简洁易读,
而且对于编程人员来说还少敲了几次键盘。
2)更好的管理属性的访问。 property
将对属性的访问直接转换为对对应的 get
,
如相关函数的调用,属性能够更好地被控制和管理,
常见的应用场景如设置校验(如检査电子邮 件地址是否合法)、
检测赋值的范围(如某个变量的赋值范围必须在0到10之间)以及对某个属性进行二次计算之后再返回给用户
(如将RGB形式表示的颜色转换为#******
形式返回给用户)或者计算某个依赖于其他属性的属性。
来看一个使用 property
控制属性访问的例子。
#!/usr/bin/python
# _*_coding: utf-8 _*_
class Date(object):
def __init__(self,year,month,day):
seif.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 <=2015 and 0 <= month <= 12 and 0 <=day <= 31):
print ("year should be in [2000:2015]")
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
实际上就是将其属性的访问与特定的函数关联起来,相对于标准属性的访问,
其工作原理如图6-5所示。property
的作用相当于一个分发器,对某个属性的访问并不直接操作具体的对象,
而对标准属性的访问没有中间这一层:直接访问存储属性的对象。
3)代码可维护性更好。 property
对属性进行再包装,以类似于接口的形式呈现给用户,
以统一的语法来访问属性,当具体实现需要改变的时候(如改变某个内部变量,或者赋值或取值的计箅发生改变),
访问的方式仍然可以保留一致。例如上述例子中,如果要更改 date
的显示方式,如“2012年4月20日”,
则只需要对get_value()
做对应的修改即可,外部程序访问 date
的方式并不需要改变,
因此代码的可维护性大大提高。
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
20
在前面的代码中只实现了 get()
方法,没有实现 set()
方法。如果使用第一种形式的 property
,
也只需要设置 x=property(get_value)
后实现对应的 get()
方法即可。
值得注意的是:使用 property
并不能真正完全达到属性只读的目的,
正如以双下划线命令的变量并不是真正的私有变量一样,
这些方法只是在直接修改属性这条道路上增加了 一些障碍,如果用户想访问私有属性,同样能够实现,
如上例便可以使用pt._PropertyTeSt__varl=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__):
fget = getattr(obj , name)
print ("fget name:"+fget.__name__ )
return fget()
fget = update_meta(__get__, fget)
if fset is not None:
def __set__(obj ,value, name=fset.__name__ ):
fset = getattr(obj,name)
print ("fset name:"+fset.__name__)
print ("setting value:" +str(value))
return fset(value)
fset = update_meta(__set__,fset)
if fdel is not None:
def __delete__(obj,name=fdel.__name__):
fdel = getattr(obj, name)
print ("warning: you are deleting attribute using fdel.__name__")
return fdel()
fdel = update_meta(__delete__, fdel)
return property(fget, fset, fdel, doc)
class C(object):
def get(self):
print ('calling C.getx to get value')
return self._x
def set(self, x):
print ('calling C.setx to set value')
self._x = x
def delete(self):
print ('calling C.delx to delete value')
del self._x
x = UserProperty(get,set,delete)
c = C()
c.x = 1
print (c.x)
del c.x
fset name:set setting value:1 calling C.setx to set value fget name:get calling C.getx to get value 1 warning: you are deleting attribute using fdel.__name__ calling C.delx to delete value
上述例子中 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)
read only
a ._Article__name="modify"
print (a.__dict__)
{'__name': 'read only', 'available': True, '_Article__name': 'modify'}
print (ROClass.__dict__)
{'__module__': '__main__', '__init__': <function ROClass.__init__ at 0x7fe503347f60>, '__dict__': <attribute '__dict__' of 'ROClass' objects>, '__weakref__': <attribute '__weakref__' of 'ROClass' objects>, '__doc__': None, 'name': <property object at 0x7fe502981260>}
print (a.name)
read only
因此得出结论,当用户再试图用a._Articcle__name
来修改变量_name
的时候并没有达到目的,
而是重新创建了新的属性_Article__name
,这样就能够很好地保护可读属性不被修改, 以免造成损失了。