zoukankan      html  css  js  c++  java
  • 流畅python学习笔记:第二十章:属性描述符:

    在前面一章中介绍了@property的用法,但是存在一个问题,如果我有多个属性想转变成property特性,那不是针对每个都需要实现一个 @propery.setter 和 @property.gettter。这样代码实现太冗余了,而且观感也不好。这一章将介绍如何将这些特性功能单独抽象出来,供各个特性共同使用。作者称这个为特性工厂函数。来看下具体如何实现的
    class Quantity(object):
        def __init__(self,storage_name):
            self.storage_name=storage_name   (2)
        def __set__(self,instance,value):
            if value > 0:
                instance.__dict__[self.storage_name]=value
            else:
                raise ValueError('value must be > 0')

    class LineItem(object):
        weight=Quantity('weight')    (1)
        price=Quantity('price')      
        def __init__(self,description,weight,price):  (3)
            self.description=description
            self.weight=weight
            self.price=price
        def subtotal(self):
            return self.weight*self.price

    if __name__=="__main__":
        truffle=LineItem('white truffle',100,1)
        truffle.weight=2
        print truffle.weight
        truffle.weight=0
    执行结果如下:
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter20.py
    Traceback (most recent call last):
      File "E:/py_prj/fluent_python/chapter20.py", line 25, in <module>
        truffle.weight=0
      File "E:/py_prj/fluent_python/chapter20.py", line 10, in __set__
        raise ValueError('value must be > 0')
    ValueError: value must be > 0
    (1)    在第一步中首先建立weight和price 两个描述符实例(Quantity),也就是托管类的类属性
    (2)    在托管实例Quantity中有个storage_name属性,用于存储托管实例中属性的名称
    (3)    在进行具体给weigh和price进行赋值的时候将会跳转到Quantity中的__set__进行赋值。此时在__set__中必须明确self和instance的关系。Self是值描述符实例也就是weight和price两个实例。而instance则是具体的托管实例也就是LineItem。当调用self.weight=weight的时候。Self赋值给__set__中的instnace.而weight实例赋值给__set__中的self。具体的weight值赋值给__set__中的value。因此在__set__中进行赋值的时候必须是instance.__dict__[self.storage_name]=value的方式。而不能用self.__dict__[self.storage_name]=value。因为instance是托管实例,因此应该把值存储在托管实例中。
    关于第三步为什么不能采用self.__dict__[self.storage_name]=value的方式,这里重点解释下。我们可能会创建多个LineItem实例,但是只有2个描述符实例weight和price。这2个描述符实例也是LineItem的类属性。如果将值存储在描述符实例中,那么数据就会变成LineItem类的类属性,也就是所有的LineItem实例都会获取这些数据。我们来看个具体的例子
    class Quantity(object):
        def __init__(self,storage_name):
            self.storage_name=storage_name
        def __set__(self,instance,value):
            if value > 0:
                self.__dict__[self.storage_name]=value   (1)
            else:
                raise ValueError('value must be > 0')

    class LineItem(object):
        weight=Quantity('weight')
        price=Quantity('price')
        def __init__(self,description,weight,price):
            self.description=description
            self.weight=weight
            self.price=price
        def subtotal(self):
            return self.weight*self.price

    if __name__=="__main__":
        truffle=LineItem('white truffle',100,1)  
        truffle1=LineItem('white truffle',100,1)   (2)
        truffle.weight=2    (3)
        print truffle1.weight.__dict__  (4)
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter20.py
    {'weight': 2, 'storage_name': 'weight'}

    (1)    从instance改成self. 此时数据将会存储在weight和price实例中

    (2)    创建2个托管实例,truffle和truffle1

    (3)    在第一个托管实例truffle中设置weigh值

    (4)    并在第二个托管实例truffle1中查看对应的值,发现weight值也变成了2。这也应证了前面说的如果数值存储在描述符实例中的话,所有LineItem都会访问到

    在上一章中讲到如果通过实例读取属性的时候,通常返回的是实例中定义的属性。但是如果实例中没有指定的属性。那么回获取类属性,而再给实例中的属性赋值的时候,通常会在实例中创建属性,根本不影响类。这种处理方式对于描述符也是一样的。根据是否定义了__set__方法,描述符分为两大类。一个是覆盖型的,一种是非覆盖型的

    来看下代码:

    class Overriding(object):
        def __get__(self, instance, owner):
            print 'get',self,instance,owner
        def __set__(self, instance, value):
            print 'get',self,instance,value

    class OverrideNoGet(object):
        def __set__(self, instance, value):
            print 'set',self,instance,value

    class NonOverriding(object):
        def __get__(self, instance, owner):
            print 'get',self,instance,owner

    class Managed(object):
        over=Overriding()
        over_no_get=OverrideNoGet()
        non_over=NonOverriding()

    实现了__set__方法的描述符属于覆盖型描述符。因此虽然描述符是类属性。但是实现__set__方法的话。会覆盖对实例属性的赋值操作。首先来看下 Overriding


    if __name__=="__main__":
        obj=Managed()    
        print obj.over      (1)
        print Managed.over     (2)
        obj.over=9                   
        obj.over               (3)
        obj.__dict__['over']=8    (4)
        print vars(obj) 
        obj.over       (5)
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter20.py
    get <__main__.Overriding object at 0x016AF450> <__main__.Managed object at 0x016AF4B0> <class '__main__.Managed'>
    None
    get <__main__.Overriding object at 0x016AF450> None <class '__main__.Managed'>
    None
    set <__main__.Overriding object at 0x016AF450> <__main__.Managed object at 0x016AF4B0> 9
    get <__main__.Overriding object at 0x016AF450> <__main__.Managed object at 0x016AF4B0> <class '__main__.Managed'>
    {'over': 8}
    get <__main__.Overriding object at 0x016AF450> <__main__.Managed object at 0x016AF4B0> <class '__main__.Managed'>
     

    (1)    obj.over触发描述符的__get__方法。第二个参数是托管实例的obj

    (2)    Managed.over触发描述符的__get__方法。第二个参数是None

    (3)    obj.over=9 触发描述符__set__方法。但是在obj.over的时候依然触发的是__get__方法

    (4)    跳过描述符,直接通过obj.__dict__属性设值

    (5)    Vars(obj)确认over的值在__dict__属性中。但是obj.over依然触发的是__get__方法。依然被描述符所覆盖

    OverrideNoGet是没有实现__get__的覆盖性描述符,因为有实现__set__。所以只有写操作由描述符处理。通过实例读取描述符会返回描述符对象本身。

    if __name__=="__main__":
        obj=Managed()
        print obj.over_no_get    (1)
        obj.over_no_get=7        (2)
        print obj.over_no_get
        obj.__dict__['over_no_get']=9     (3)
        print obj.over_no_get              (4)
        obj.over_no_get=7
        print obj.over_no_get               (5)

    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter20.py

    <__main__.OverrideNoGet object at 0x0167F470>

    set <__main__.OverrideNoGet object at 0x0167F470> <__main__.Managed object at 0x0167F4B0> 7

    <__main__.OverrideNoGet object at 0x0167F470>

    9

    set <__main__.OverrideNoGet object at 0x0167F470> <__main__.Managed object at 0x0167F4B0> 7

    9

    (1)    因为没有__get__方法,因为从类中获取实例属性

    (2)    obj.over_no_get=7触发__set__方法

    (3)    通过实例的__dict__属性设置名为over_no_get的实例属性

    (4)    访问obj.over_no_get的时候实例属性会覆盖描述符

    (5)    但是在obj.over_no_get=7依然触发的是__set__方法。只有在读取的时候,只要有同名的实例属性,描述符就会被遮盖

    最后来看下NonOverriding,只实现了__get__方法,而没有__set__方法。属于非覆盖型描述符。如果设置了同名的实例属性。描述符会被覆盖。致使描述符无法处理那个实例的的属性。
    obj=Managed()
    obj.non_over    (1)
    obj.non_over=9   (2)
    print obj.non_over  (3)
    del obj.non_over
    obj.non_over   (4)
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter20.py
    get <__main__.NonOverriding object at 0x016BF470> <__main__.Managed object at 0x016BF490> <class '__main__.Managed'>
    9
    get <__main__.NonOverriding object at 0x016BF470> <__main__.Managed object at 0x016BF490> <class '__main__.Managed'>

    (1)    obj.over触发描述符的__get__方法。

    (2)    因为没有__set__方法。Obj.non_over=9的时候无法触发__set__方法。

    (3)    访问obj.non_over的时候访问的是实例属性,而把Managed类的同名描述符属性遮盖掉

    (4)    在删除了obj.non_over的时候,也就是对应的实例属性被删除了。再次访问obj.non_over的时候,会触发描述符__get__方法。

  • 相关阅读:
    Icident event 分析
    innodb buffer pool相关特性
    备库Seconds_Behind_Master的计算
    savepoint原理
    layer探框
    解决layui表格和下拉框同时使用时,下拉框被表格遮当问题
    layui switch开关按钮
    formSelects 4.x多选下拉框
    java如何调用php接口,并获取值
    layui时间范围选择器
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/7479233.html
Copyright © 2011-2022 走看看