>>> from env_helper import info; info()
页面更新时间: 2023-12-27 10:42:56
运行环境:
Linux发行版本: Debian GNU/Linux 12 (bookworm)
操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
Python版本: 3.11.2
6.8. 使用更为安全的property¶
property是用来实现属性可管理性的built-in数据类型(注意:很多地方将property称为
函数, 我个人认为这是不恰当的,它实际上是一种实现了 __get__()
、
__set__()
方法的类,
用户也可以根据A己的需要定义个性化的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 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)
>>> 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 Traceback (most recent call last)
<ipython-input-4-667264f7c196> in <module>()
16 print (obj.x + 2)
17 del obj.x
---> 18 obj.x
<ipython-input-4-667264f7c196> in get_value(self)
4 def get_value(self):
5 print ("calling get method to return value")
----> 6 return self._somevalue
7 def set_value(self, value):
8 print ("calling set method to set value")
AttributeError: 'Some_Class' object has no attribute '_somevalue'
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的优势可以简单地概括为以下儿点:
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的作用相当于一个分发器.对某个属性的访问丼 不直接操作具体的对象,而对标准属性的i方问没有中间这一层:直接访问存储属性的对象。
3)代码可维护性更好。property对属性进行再包装,以类似于接口的形式呈现给用户,
以统一的语法来访问属性,当具体实现需要改变的时候(如改变某个内部变量,或者赋值或
取值的计箅发生改变),访问的方式仍然可以保留一致。例如上述例子中,如果要更改date
的显示方式f如“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
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-14-0e7e58c9db78> in <module>()
7 pt = PropertyTest()
8 print(pt.x)
----> 9 pt.x=12
AttributeError: can't set attribute
在前面的代码中我们只实现了 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)
>>> 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 0x7f2a800896a8>, '__dict__': <attribute '__dict__' of 'ROClass' objects>, '__weakref__': <attribute '__weakref__' of 'ROClass' objects>, '__doc__': None, 'name': <property object at 0x7f2a80087f98>}
read only
我们发现,当用户再试图用a._Articcle__name
来修改变量_name
的时候并没有达到目
的,而是重新创建了新的属性_Article__name
,这样就能够很好地保护可读属性不被修改,
以免造成损失了。