在前面一章中介绍了@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.11\python.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.11\python.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.11\python.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.11\python.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.11\python.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__方法。

流畅python学习笔记:第二十章:属性描述符:的更多相关文章

  1. 流畅的python第二十章属性描述符学习记录

    描述符是对多个属性运用相同存取逻辑的一种方式.例如,Django ORM 和 SQL Alchemy等 ORM 中的字段类型是描述符,把数据库记录中字段里的数据与 Python 对象的属性对应起来.描 ...

  2. 流畅python学习笔记:第十四章:迭代器和生成器

    迭代器和生成器是python中的重要特性,本章作者花了很大的篇幅来介绍迭代器和生成器的用法. 首先来看一个单词序列的例子: import re re_word=re.compile(r'\w+') c ...

  3. 流畅python学习笔记:第十章:序列的修改,散列和切片

    前面在介绍了类的很多内置方法,比如__add__,__eq__,这里继续介绍类的两个内置方法,这2个内置方法可以将一个类实例变成一个序列的形式.代码如下 class vector(object):   ...

  4. Python学习笔记第二十五周(Django补充)

    1.render_to_reponse() 不同于render,render_to_response()不用包含request,直接写template中文件 2.locals() 如果views文件中 ...

  5. 菜鸟Python学习笔记第二天:关于Python黑客。

    2016年1月5日 星期四 天气:还好 一直不知道自己为什么要去学Python,其实Python能做到的Java都可以做到,Python有的有点Java也有,而且Java还是必修课,可是就是不愿意去学 ...

  6. Python学习笔记第二十六周(Django补充)

    一.基于jQuery的ajax实现(最底层方法:$.jax()) $.ajax( url: type:''POST“ ) $.get(url,[data],[callback],[type])  #c ...

  7. Python学习笔记第二十三周(Flask架构)

    目录: 一.变量引用 内容: 备注:PyCharm小技巧,comm+alt+l  自动修改格式,comm+alt+return  向上添加新行 一.变量引用 1.url生成 from flask im ...

  8. python学习笔记第二周

    目录 一.基础概念 1.模块 1)os模块 2)sys模块 2.pyc文件 3.数据类型 1)数字 2)布尔值 3)字符串 4.数据运算 5.运算符 6.赋值运算 7.逻辑运算 8.成员运算 9.身份 ...

  9. 流畅python学习笔记:第十一章:抽象基类

    __getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...

随机推荐

  1. shell 实现自动备份nginx下的站点

    shell 实现自动备份nginx下的站点 优点 实现自动备份ngnix下的所有运行的站点 自定义排除备份站点,支持三种排除 自动维护备份目录,防止备份目录无限扩大 备份压缩tar.gz格式 源码: ...

  2. http协议中connection头的作用

    在http1.1中request和reponse header中都有可能出现一个connection的头,此header的含义是当client和server通信时对于长链接如何进行处理.   在htt ...

  3. 228. 汇总区间(leetcode)

    #整体思路:使用堆栈,在Python中可以使用列表代替:如果a[i]-a[i-1]==1,就要将a[i]合并到之前的区间里,#所以我们队首位元素开辟一个区间为[a[0],a[0]]#做最后汇总时候,如 ...

  4. 解决Linux下AES解密失败

    前段时间,用了个AES加密解密的方法,详见上篇博客AES加密解密. 加解密方法在window上測试的时候没有出现不论什么问题.将加密过程放在安卓上.解密公布到Linuxserver的时候,安卓将加密的 ...

  5. 白盒测试中如何实现真正意义上并发测试(Java)

    在这个话题开始之前,首先我们来弄清楚为什么要做并发测试? 一般并发测试,是指模拟并发访问,测试多用户并发访问同一个应用.模块.数据时是否产生隐藏的并发问题,如内存泄漏.线程锁.资源争用问题. 站在性能 ...

  6. @Cacheable注解在spring3中的使用-实现缓存

    转:  http://blog.csdn.net/chenleixing/article/details/44815443 在软件开发中使用缓存已经有一个非常久的历史了.缓存是一种很好的设计思想,一旦 ...

  7. Linux下的定时任务Crontab

    通过crontab -e写入定时任务的指令,一行为一项任务. 任务模式是时间克龙表达式+命令形式. 如: 2 0,6,12,18 * * * perl /root/restarttomcat.pl p ...

  8. vue2.0 仿手机新闻站(一)项目开发流程

    vue仿手机新闻站: 1.拿到静态页面,直接用vue边布局,边写 2.假数据 没有用任何UI组件,切图完成 做项目基本流程: 1.规划组件结构 Nav.vue Header.vue Home.vue ...

  9. WPF自定义依赖集合属性无法触发更新的问题

    通常WPF中通过继承UserControl的来快速创建自定义控件,最近项目上需要设计一个卫星星图显示控件,最终效果如下图所示.完成过程中遇到了自定义集合依赖属性无法触发更新通知的问题,在此记录一下,方 ...

  10. initramfs扫描磁盘前改变磁盘上电顺序

    背景: 机械硬盘需要12V 5V电源,此前设计是硬件电路默认5V有效.12V无效,然后系统通过驱动上12V电,对磁盘来说相当于先上5V后上12V,这种方式对大部分磁盘是可以的,但对于日立 HGST磁盘 ...