python cookbook第三版学习笔记十三:类和对象(四)描述器
__get__以及__set__:假设T是一个类,t是他的实例,d是它的一个描述器属性。读取属性的时候T.d返回的是d.__get__(None,T),t.d返回的是d.__get__(t,T).说法比较绕,我们来看一个实例:
class Descriptor(object):
def __get__(self, instance, owner):
return 'get',self,instance,owner class T(object):
d=Descriptor()
if __name__ == "__main__":
t=T()
print t.d
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
('get', <__main__.Descriptor object at 0x017EB7D0>, <__main__.T object at 0x017EB7F0>, <class '__main__.T'>)
在这里可以看到Descriptor中实现了__get__方法。因此属于一个描述器。在T中引用这个描述器,在调用t.d的时候,实际上调用的__get__方法。__get__方法中instance是实例t,owner是对象T。self就是对象Descriptor
我们来看下书中的例子:
class integer(object):
def __init__(self,name):
self.name=name
def __get__(self, instance, owner):
if instance is None:
return self
else:
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value,int):
raise TypeError('Expected an int')
instance.__dict__[self.name]=value
def __delete__(self, instance):
del instance.__dict__[self.name] class Point(object):
x=integer('x')
y=integer('y')
def __init__(self,value1,value2):
self.x=value1
self.y=value2
if __name__ == "__main__":
p=Point(2,3)
print p.x
这里首先在Point中定义了2个类属性变量,x和y,分别是对应的是interger对象。而interger实现了__set__以及__get__。因此属于描述器。对x,y和的调用将会用到__set__以及__get__方法。通过上面的可以看到在print p.x的时候实际上调用的是integer中的__get__方法。Instnace对应的是p。从这个实现可以看到当一个类变量被定义为描述器的时候,对这个类变量的操作将由描述器来进行代理。
这里介绍了描述器的用法,那么描述器用在哪些方面呢。描述器有什么用途呢。我们首先来看下这个例子:
如果我们在做一个学生的考试系统。
class student_score(object):
def __init__(self,id,score,rating):
self.id=id
self.score=score
self.rating=rating
def get_result(self):
return self.score*self.rating
if __name__ == "__main__":
s=student_score(1,90,0.9)
print s.get_result()
当我们在调用每一个student_score实例的时候,会传入每个学生的id,分数,等级。然后调用get_result得到分数。但是如果一下手误把分数或者等级输成负值比如s=student_score(1,-90,0.9)或者是s=student_score(1,90,-0.9)。这个学生的成绩就变成了个负值。这明显不合理啊。那么如何规避呢。代码改成如下:在__init__中进行保护
class student_score(object):
def __init__(self,id,score,rating):
self.id=id
if score < 0 or rating <0:
raise ValueError('parameter could not be negative')
else:
self.score=score
self.rating=rating
def get_result(self):
return self.score*self.rating
if __name__ == "__main__":
s=student_score(1,-90,0.9)
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
Traceback (most recent call last):
File "E:/py_prj/python_cookbook/chapter8.py", line 156, in <module>
s=student_score(1,-90,0.9)
File "E:/py_prj/python_cookbook/chapter8.py", line 147, in __init__
raise ValueError('parameter could not be negative')
ValueError: parameter could not be negative
这样在执行的时候,由于导入的是一个负值。因此报错。
但是如果我们在初始调用的时候是正确的,但是后续有人改变了这个参数呢。比如下面的代码。s.score=-90.直接将成绩修改了。
if __name__ == "__main__":
s=student_score(1,90,0.9)
s.score=-90
print s.get_result()
这种在实例中修改的场景如何规避呢?一般会想到3种方法:
方法一:将score和rating设置为私有变量。通过get_score和set_score的方式来进行调用,然后在set_score中进行保护。代码如下。这种方法确实有效。但是如果我们要对多个变量进行保护,那不得对所有的变量都写个设置的函数。这样的代码的可读性就太差了
class student_score(object):
def __init__(self,id,score,rating):
self.id=id
if score < 0 or rating <0:
raise ValueError('parameter could not be negative')
else:
self.__score=score
self.__rating=rating
def get_result(self):
return self.__score*self.__rating
def get_score(self):
return self.__score
def set_score(self,value):
if value < 0:
raise ValueError('parameter could not be negative')
else:
self.__score=value
方法二:
用@property的方法。在这里
class student_score(object):
def __init__(self,id,score,rating):
self.id=id
if score < 0 or rating <0:
raise ValueError('parameter could not be negative')
else:
self._score=score
self._rating=rating
def get_result(self):
return self._score*self._rating
@property
def score(self):
return self._score
@score.setter
def score(self,value):
if value < 0:
raise ValueError('parameter could not be negative')
else:
self._score=value
if __name__ == "__main__":
s=student_score(1,90,0.9)
s.score=-91
使用了property之后,score函数可以当成属性值一样调用。并在调用s.score的时候将会调用score.setter进行值的判断。但是使用@property和方法一同样的面对一个问题就是如果需要对多个变量进行赋值保护。在需要些多个setter。这样的代码也很繁琐
方法3:
重写__setattr__。class student_score(object):
def __init__(self,id,score,rating):
self.id=id
if score < 0 or rating <0:
raise ValueError('parameter could not be negative')
else:
self.score=score
self.rating=rating
def get_result(self):
return self.score*self.rating
def __setattr__(self, key, value):
if key == 'score' or key == 'rating':
if value < 0:
raise ValueError('parameter could not be negative')
else:
self.__dict__[key]=value if __name__ == "__main__":
s=student_score(1,90,0.9)
s.score=-91
在这种三种方法中。重写__setattr__是代码量最小的一种。在__setattr__对变量名进行判断。当变量名属于要判断的对象的时候。对值进行判断。这种用法比之前的两种方法要方便了很多。但是在__init__依然要进行判断,且还要重写__setattr__方法。如果有多个类,其中都有score或者rating要进行异常判断,哪所有的类都需要增加异常保护。有没有一种方法可以让我在实例中只进行过程处理,而用另外通过的方法进行异常保护?这里是描述器要解决的问题。
class score_descrptor(object):
def __init__(self):
pass
def __get__(self, instance, owner):
if instance is None:
return self
else:
return instance.__dict__['score']
def __set__(self, instance, value):
if value < 0:
raise ValueError('parameter could not be negative')
instance.__dict__['score']=value class student_score(object):
score=score_descrptor()
def __init__(self,id,score,rating):
self.id=id
if score < 0 or rating < 0:
raise ValueError('parameter could not be negative')
self.score=score
self.rating=rating
def get_result(self):
return self.score*self.rating if __name__ == "__main__":
s=student_score(1,90,0.9)
print s.score
s.score=-91
上面的代码score_descrptor是一个描述器。当调用s.score的时候。执行的是score_descrptor.__get__。当调用s.score=-91的时候,执行的是score_descrptor.__set__. 通过这样的方法我们就将score的判断方法变成了一个通用的方法。只要任何类中需要判断score的正负的时候。都可以调用score_descrptor
来看一个描述器在延迟计算属性上的应用。我们希望将一个属性访问的时候计算结果,并且一旦被访问后,结果就被缓存起来,后续也可以继续调用,而不是每次都去计算
class lazyproperty(object):
def __init__(self,func):
self.func=func
def __get__(self,instance,cls):
if instance is None:
return self
else:
value=self.func(instance)
setattr(instance,self.func.__name__,value)
return value class Circle(object):
def __init__(self,radius):
self.raidus=radius
@lazyproperty
def area(self):
print 'computing area'
return math.pi*self.raidus**2 if __name__ == "__main__":
c= Circle(4.0)
print c.raidus
print c.area
print c.__dict__
1 首先在@lazyproperty的时候就等价在Circle 定义了area= lazyproperty(area)。并且将func赋值为area。在调用c.area将会跳到__get__去执行。
2 value=self.func(instance)传入Circle的实例,实际上执行的是area函数。
3 此时self.func.__name__=area, 通过setattr(instance,self.func.__name__,value)给Circle实例添加一个area的属性,并且这个属性的值是area函数的返回值
通过执行结果也可以看到在Cirlce的字典中也增加了area的属性。
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
4.0
computing area
50.2654824574
{'raidus': 4.0, 'area': 50.26548245743669}
python cookbook第三版学习笔记十三:类和对象(四)描述器的更多相关文章
- python cookbook第三版学习笔记十三:类和对象(三)描述器
__get__以及__set__:假设T是一个类,t是他的实例,d是它的一个描述器属性.读取属性的时候T.d返回的是d.__get__(None,T),t.d返回的是d.__get__(t,T).说法 ...
- python cookbook第三版学习笔记十五:property和描述
8.5 私有属性: 在python中,如果想将私有数据封装到类的实例上,有两种方法:1 单下划线.2 双下划线 1 单下划线一般认为是内部实现,但是如果想从外部访问的话也是可以的 2 双下划线是则无法 ...
- python cookbook第三版学习笔记十:类和对象(一)
类和对象: 我们经常会对打印一个对象来得到对象的某些信息. class pair: def __init__(self,x,y): self.x=x self. ...
- python cookbook第三版学习笔记六:迭代器与生成器
假如我们有一个列表 items=[1,2,3].我们要遍历这个列表我们会用下面的方式 For i in items: Print i 首先介绍几个概念:容器,可迭代对象,迭代器 容器是一种存储数据 ...
- python cookbook第三版学习笔记 一
数据结构 假设有M个元素的列表,需要从中分解出N个对象,N<M,这会导致分解的值过多的异常.如下: record=['zhf','zhf@163.com','775-555-1212','847 ...
- python cookbook第三版学习笔记二十:可自定义属性的装饰器
在开始本节之前,首先介绍下偏函数partial.首先借助help来看下partial的定义 首先来说下第一行解释的意思: partial 一共有三个部分: (1)第一部分也就是第一个参数,是一个函数, ...
- python cookbook第三版学习笔记十六:抽象基类
假设一个工程中有多个类,每个类都通过__init__来初始化参数.但是可能有很多高度重复且样式相同的__init__.为了减少代码.我们可以将初始化数据结构的步骤归纳到一个单独的__init__函数中 ...
- python cookbook第三版学习笔记七:python解析csv,json,xml文件
CSV文件读取: Csv文件格式如下:分别有2行三列. 访问代码如下: f=open(r'E:\py_prj\test.csv','rb') f_csv=csv.reader(f) for f in ...
- python cookbook第三版学习笔记十一:类和对象(二)调用父类的方法
在子类中调用父类的方法,可以下面的A.spam(self)的方法. class A(object): def spam(self): print 'A.spam' class ...
随机推荐
- Java 字符集,编码、解码
1. 计算机中文件.数据底层都是基于二进制的. 计算机底层并没有文本文件.图片文件之分,它只是记录着每个文件的二进制序列. 字符集:包含着字符和二进制序列之间的对应关系,一个字符对应一个二进制序列. ...
- mac与linux服务器之间使用ssh互通有无
1. 在mac上没有找到好用的shell图形界面的软件,但也是有办法的,使用ssh公钥达到互相有无目的 2.场景是mac连A(linux,以下简称A)服务器 3.登陆mac shell ,按comma ...
- linux svn co 重新迁出
在linux环境下,使用svn co (即svn checkout) 报svn: Authorization failed错误, 使用svn co svn://localhost/temp.cc /d ...
- devicemapper: Error running deviceCreate (ActivateDevice) dm_task_run failed
在一台新机子上面,docker处理完lvs数据卷之后,启动docker服务时,出现了启动失败,失败信息如下: [root@hxin221 ~]# systemctl status docker ● d ...
- win7安装mysql解压缩版
http://blog.csdn.net/u013067166/article/details/49951577
- HDU2550 百步穿杨
百步穿杨 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submi ...
- Spark Streaming的样本demo统计
废话不多说,直接上代码 package com.demo; import java.util.List; import java.util.regex.Pattern; import org.apac ...
- python 制作wordcloud词云
pip install wordcloud 需要用到numpy pillow matplotlib 安装完成以后 wordcloud_cli --text in.txt --imagefile ou ...
- Vue 引入ElementUI 2.0.11:依赖未发现的问题
转自:https://blog.csdn.net/cslucifer/article/details/79019649
- java拷贝构造函数
浅拷贝就是指两个对象共同拥有同一个值,一个对象改变了该值,也会影响到另一个对象. 深拷贝就是两个对象的值相等,但是互相独立. 构造函数的参数是该类的一个实例. Operator = 拷贝构造函数 ...