>>> 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方问没有中间这一层:直接访问存储属性的对象。

_images/img5.png

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,这样就能够很好地保护可读属性不被修改, 以免造成损失了。