- 译者序
- 前言
- 第1章 问答环节
- 第2章 Python 如何运行程序
- 第3章 如何运行程序
- 第4章 介绍 Python 对象类型
- 第5章 数字
- 第6章 动态类型简介
- 第7章 字符串
- 第8章 列表与字典
- 第9章 元组、文件及其他
- 第10章 Python 语句简介
- 第11章 赋值、表达式和打印
- 第12章 if 测试和语法规则
- 第13章 while 和 for 循环
- 第14章 迭代器和解析,第一部分
- 第15章 文档
- 第16章 函数基础
- 第17章 作用域
- 第18章 参数
- 第19章 函数的高级话题
- 第20章 迭代和解析,第二部分
- 第21章 模块:宏伟蓝图
- 第22章 模块代码编写基础
- 第23章 模块包
- 第24章 高级模块话题
- 第25章 OOP:宏伟蓝图
- 第27章 更多实例
- 第28章 类代码编写细节
- 第29章 运算符重载
- 第30章 类的设计
- 第31章 类的高级主题
- 第32章 异常基础
- 第34章 异常对象
- 第35章 异常的设计
- 第36章 Unicode 和字节字符串
- 字符串基础知识
- Python 的字符串类型
- 文本和二进制文件
- Python 3.0 中的字符串应用
- 转换
- 编码 Unicode 字符串
- 编码非ASCII文本
- 编码和解码非ASCII文本
- 其他 Unicode 编码技术
- 转换编码
- 在 Python 2.6 中编码 Unicode 字符串
- 源文件字符集编码声明
- 使用 Python 3.0 Bytes 对象
- 序列操作
- 创建 bytes 对象的其他方式
- 混合字符串类型
- 使用 Python 3.0(和 Python 2.6)bytearray 对象
- 使用文本文件和二进制文件
- Python 3.0 中的文本和二进制模式
- 类型和内容错误匹配
- 使用 Unicode 文件
- 在 Python 3.0 中处理 BOM
- Python 2.6 中的 Unicode 文件
- Python 3.0 中其他字符串工具的变化
- Struct二进制数据模块
- pickle对象序列化模块
- XML解析工具
- 本章小结
- 本章习题
- 习题解答
- 第37章 管理属性
- 第38章 装饰器
- 第39章 元类
- 附录A 安装和配置
- 附录B 各部分练习题的解答
- 作者介绍
- 封面介绍
描述符
从功能上讲,描述符协议允许我们把一个特定属性的get和set操作指向我们提供的一个单独类对象的方法:它们提供了一种方式来插入在访问属性的时候自动运行的代码,并且它们允许我们拦截属性删除并且为属性提供文档(如果愿意的话)。
描述符作为独立的类创建,并且它们就像方法函数一样分配给类属性。和任何其他的类属性一样,它们可以通过子类和实例继承。通过为描述符自身提供一个self,以及提供客户类的实例,都可以提供访问拦截方法。因此,它们可以自己保留和使用状态信息,以及主体实例的状态信息。例如,一个描述符可能调用客户类上可用的方法,以及它所定义的特定于描述符的方法。
和特性一样,描述符也管理一个单个的、特定的属性。尽管它不能广泛地捕获所有的属性访问,但它提供了对获取和赋值访问的控制,并且允许我们自由地把简单的数据修改为计算值从而改变一个属性,而不会影响已有的代码。特性实际上只是创建一种特定描述符的方便方法,并且,正如我们所见到的,它们可以直接作为描述符编写。
然而,特性的应用领域相对狭窄,描述符提供了一种更为通用的解决方案。例如,由于它们编码为常规类,所以描述符拥有自己的状态,可能参与描述符继承层级,可以使用复合来聚合对象,并且为编写内部方法和属性文档字符串提供一种自然的结构。
基础知识
正如前面所提到的,描述符作为单独的类编写,并且针对想要拦截的属性访问操作提供特定命名的访问器方法——当以相应的方式访问分配给描述符类实例的属性时,描述符类中的获取、设置和删除等方法自动运行:
带有任何这些方法的类都可以看作是描述符,并且当它们的一个实例分配给另一个类的属性的时候,它们的这些方法是特殊的——当访问属性的时候,会自动调用它们。如果这些方法中的任何一个空缺,通常意味着不支持相应类型的访问。然而,和特性不同,省略一个__set__意味着允许这个名字在一个实例中重新定义,因此,隐藏了描述符——要使得一个属性是只读的,我们必须定义__set__来捕获赋值并引发一个异常。
描述符方法参数
在进行任何真正的编程之前,先来回顾一些基础知识。前面小节介绍的所有3种描述符方法,都传递了描述符类实例(self)以及描述符实例所附加的客户类的实例(instance)。
__get__访问方法还额外地接收一个owner参数,指定了描述符实例要附加到的类。其instance参数要么是访问的属性所属的实例(用于instance.attr),要么当所访问的属性直接属于类的时候是None(用于class.attr)。前者通常针对实例访问计算一个值;如果描述符对象访问是受支持的,后者通常返回self。
例如,在下面的例子中,当获取X.attr的时候,Python自动运行Descriptor类的__get__方法,Subject.attr类属性分配给该方法(和特性一样,在Python 2.6中,要在这里使用描述符,我们必须派生自对象;在Python 3.0中,这是隐式的,但无伤大雅):
注意在第一个属性获取中自动传递到__get__方法中的参数,当获取X.attr的时候,就好像发生了如下的转换(尽管这里的Subject.attr没有再次调用__get__):
当描述符的实例参数为None的时候,该描述符知道将直接访问它。
只读描述符
正如前面提到的,和特性不同,使用描述符直接忽略__set__方法不足以让属性成为只读的,因为描述符名称可以赋给一个实例。在下面的例子中,对X.a的属性赋值在实例对象X中存储了a,由此,隐藏了存储在类C中的描述符:
这就是Python中所有实例属性赋值工作的方式,并且它允许在它们的实例中类选择性地覆盖类级默认值。要让基于描述符的属性成为只读的,捕获描述符类中的赋值并引发一个异常来阻止属性赋值——当要赋值的属性是一个描述符的时候,Python有效地绕过了常规实例层级的赋值行为,并且把操作指向描述符对象:
注意:还要注意不要把描述符__delete__方法和通用的__del__方法搞混淆了。调用前者是试图删除所有者类的一个实例上的管理属性名称;后者是一种通用的实例析构器方法,当任何类的一个实例将要进行垃圾回收的时候调用。__delete__与我们将要在本章后面遇到的__delattr__泛型属性删除方法关系更近。参见本书第29章了解关于操作符重载的更多内容。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论