13.16新式类的高级特性(python 2.2+)
这一整节都很难,尤其是描述符部分。
一般而言,我们对类属性或是实例属性的访问、赋值和删除,不会特别的关注。
按本小白的理解,描述符则是针对这一过程的抽象。
描述符首先是一个类,这个类必须至少实现__get__()(访问)、__set__()(赋值)或__delete__()(删除)三者其中之一。
同时覆盖__get__()和__set__()的类成为数据描述符,而没有覆盖__set__()的类成为非数据描述符。
之后,描述符这个类的实例作为另外一个类的类属性。
使用描述符可以对属性访问、属性赋值和属性删除作出一定操作,比如说属性只能只读,不能修改和删除。
接下来是书中四个例子。
#-*-coding: utf-8-*- class DevNull1(object): # 描述符,禁止对属性进行访问 def __get__(self, obj, typ = None): # __get__()用于得到属性值 pass def __set__(self, obj, val): # __set__()用于给属性赋值,还有__del__()表示删除某个属性,有__set__()的成为数据描述符,无__set__()的成为非数据描述符 pass class C1(object): foo = DevNull1() # 类C1中有属性foo,foo又恰巧是类DevNull1的一个实例。 if __name__ == "__main__": c1 = C1() c1.foo = 'bar' print 'c1.foo contains:', c1.foo # 显然实例c1无法获取属性foo的值,因为数据描述符比实例属性具有更高的优先级 C1.foo = 'bar' print 'C1.foo contains:', C1.foo # 这是类属性C1.foo的属性值为'bar',类属性比数据描述符具有更高的优先级#-*-coding: utf-8-*- class DevNull2(object): # 描述符,禁止对属性进行访问 def __get__(self, obj, typ = None): print 'Accessing attribute...ignoring' def __set__(self, obj, val): print 'Attempt to assign %r...ignoring' % (val) class C2(object): foo = DevNull2() # 类C2中有属性foo,foo是类DevNull2的一个实例。 if __name__ == "__main__": c2 = C2() c2.foo = 'bar' x = c2.foo print 'c2.foo contains:', c2.foo # 显然实例c2无法获取属性foo的值 print 'C2.foo contains:', C2.foo # 同样类C2也无法获取属性foo的值 #-*-coding: utf-8-*- class DevNull3(object): # 描述符,禁止对属性进行访问 def __init__(self, name = None): # 添加占位符,包含这个描述符的有用信息 self.name = name def __get__(self, obj, typ = None): print 'Accessing [%s]...ignoring' % (self.name) def __set__(self, obj, val): print 'Assigning %r to [%s]...ignoring' % (val, self.name) class C3(object): foo = DevNull3('foo') # 类C3中有属性foo,foo又恰巧是类DevNull3的一个实例。 if __name__ == "__main__": c3 = C3() c3.foo = 'bar' x = c3.foo print 'c3.foo contains:', x # 显然实例c3无法获取属性foo的值 print "Let us try to sneak it into c3 instance..." c3.__dict__['foo'] = 'bar' x = c3.foo print 'c3.foo contains:', x print "c3.__dict__['foo'] contains: %r" % c3.__dict__['foo'], "...Why?!?" # c3这个实例具有特殊属性__dict__,这个属性是一个字典,它存储了实例c3的属性和对应的值,但在之前的c3.__dict__['foo'] = 'bar'中,对__dict__这个属性进行了修改,增加了一个键值对,而不是c3这个实例的foo属性的值是'bar',foo属性仍然是不可访问的。
#-*-coding: utf-8-*- # 使用文件来存储属性 import os import pickle class FileDescr(object): # 描述符类 saved = [] # 描述符类里有属性saved,记录描述符访问过的所有属性 def __init__(self, name=None): self.name = name def __get__(self, obj, typ=None): if self.name not in FileDescr.saved: # 确保用户赋值后才能使用 raise AttributeError, "%r used before assignment" % self.name try: f = open(self.name, 'r') # 尝试打开pickle文件,读取保存的值 val = pickle.load(f) f.close() return val except (pickle.UnpicklingError, IOError, EOFError, AttributeError, ImportError, IndexError), e: raise AttributeError, "could not read %r: %s" % self.name def __set__(self, obj, val): # 将属性保存到文件 f = open(self.name, 'w') try: pickle.dump(val, f) # 注册属性名,使用户可以读取这些属性值 FileDescr.saved.append(self.name) except (TypeError, pickle.PicklingError), e: raise AttributeError, "could not pickle %r" % self.name finally: f.close() def __delete__(self, obj): # 如果属性被删除,文件会被删除,属性名字也会被删除。 try: os.unlink(self.name) FileDescr.saved.remove(self.name) except (OSError, ValueError), e: pass class MyFileVarClass(object): foo = FileDescr('foo') bar = FileDescr('bar') if __name__ == "__main__": fvc = MyFileVarClass() # print fvc.foo # 属性没有被赋值,引发__get__()中的AttributeError异常 fvc.foo = 42 fvc.bar = 'leanna' print fvc.foo, fvc.bar del fvc.foo # print fvc.foo, fvc.bar # 删除属性fvc.foo后,显然引发__get__()中的AttributeError异常 最后一个例子,本人也看得不是很明白。