使用更加安全的property

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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值