property
前言
property是用来实现属性可管理性的bulit-in数据类型(注意:很多地方将property称为函数,个人认为不是很恰当,它实际上是一种实现了__get__()、set()方法的类,用户可以根据自己的需要个性化定义property),其实质是一种特殊的数据描述符(数据描述符:如果一个对象同时定义了__get__和__set__方法,则成为数据描述符,如果仅定义了__get__方法,则称为非数据描述符)。它和普通数据描述符的区别是,它提供了一种高级的控制属性访问的机制,它以标注库的形式提供描述符的实现。
一、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 del_attr(self):
print ('calling delete method to delete value')
del self._somevalue
x = property(get_value,set_value,del_attr,'i am the property')
obj = Some_Class()
obj.x = 10
print (obj.x+2)
del obj.x
obj.x
输出:
calling set method to set value
calling get method to return value
12
calling delete method to delete value
calling get method to return value
…
AttributeError: ‘Some_Class’ object has no attribute ‘_somevalue’
2. 第二种形式:
class Some_property(object):
_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
obj = Some_property()
obj.x = 10
print (obj.x+2)
del obj.x
print (obj.x)
输出:
calling set method to set value
calling get method to return value
12
calling delete method to delete value
calling get method to return value
None
二、property的优点
1.代码更简洁,可读性更强
如:obj.x += 1 比 obj.set_value(obj.get_value() + 1)要更简洁和易读。
2.更好的管理属性访问
property将对属性的访问直接转为对应的get、set等相关函数的调用,属性能够更好的被管理和控制。如下:
class Date:
def __init__(self, year, month, day):
self.year = str(year)
self.month = str(month)
self.day = str(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 <= int(year) <= 2021 and 1 <= int(month) <= 12 and 1 <= int(day) <= 31):
print('date.value error')
raise AssertionError
self.year = year
self.month = month
self.day = day
date = property(get_date, set_date)
d1 = Date(2009,10,8)
print(d1.get_date())
d1.set_date('2000-5-1')
d1.get_date()
输出:
2009-10-8
‘2000-5-1’
3.代码可维护性更好
property对属性进行再包装,以类似于接口的形式呈现给用户,以统一的语法来访问属性,当具体实现需要改变的时候,访问的方式仍然可以保留一致。例如上面例子中,如果更改date的显示方式,如‘2021年8月5号’,则只需要对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-------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in ()
7 pt = PropertyTest()
8 print (pt.x)
----> 9 pt.x = 12
AttributeError: can’t set attribute
其实我们还是可以修改其属性值的:
pt._PropertyTest__var1 = 30
print (pt.x)
30
如何真正的实现属性的只读呢?
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)
输出:
read only
{’__name’: ‘read only’, ‘available’: True, ‘_Article__name’: ‘modify’}
{‘module’: ‘main’, ‘init’: <function ROClass.init at 0x000001CD34EC0048>, ‘dict’: <attribute ‘dict’ of ‘ROClass’ objects>, ‘weakref’: <attribute ‘weakref’ of ‘ROClass’ objects>, ‘doc’: None, ‘name’: <property object at 0x000001CD34EFDE58>}
read only
三、根据需求,自定property
def update_meta(self,other):
self.__name__ = other.__name__
self.__doc__ = other.__doc__
self.__dict__ = 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 attr useing fdel.__del__')
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.sets 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.sets to set value
fget name:get
calling C.getx to get value
1
warning you are deleting attr useing fdel.del
上述例子中,UserProperty继承property,其构造函数__new__(cls,fget=None,fset=None,fdel=None,doc=None)中重新定义了fget()、fset()、fdel()方法以满足用户特定的需要,最后返回的对象实际还是property的实例,因此用户可以能够想使用property一样使用UserProperty。