- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
11.12 延伸阅读
Beazley 与 Jones 的《Python Cookbook(第 3 版)中文版》有一节(8.12)定义了一个抽象基类。这本书在 Python 3.4 之前撰写,因此他们没有使用现在推荐的句法,即通过继承 abc.ABC 声明抽象基类,而是使用 metaclass 关键字。除了这个小细节之外,那个秘笈很好地涵盖了抽象基类的主要功能,而且最后还给出了宝贵的意见,即前一节末尾引用的那段话。
Doug Hellmann 写的《Python 标准库》一书中有一章是关于 abc 模块的。Doug 创建的 PyMOTW(Python Module of the Week)网站中也有那一章。这本书和 PyMOTW 网站都针对 Python 2,因此如果你使用 Python 3 的话,必须做些调整。22 记住,在 Python 3.4 中,唯一推荐使用的抽象基类方法装饰器是 @abstractmethod,其他装饰器已经废弃了。本章小结中引用的关于抽象基类的另一句话出自 Doug 的网站和这本书。
22PyMOTW 网站现在已经是面向 Python 3 了。——编者注
使用抽象基类时,经常会遇到多重继承,而且是不可避免的,因为基本的集合抽象基类(Sequence、Mapping 和 Set)都扩展多个抽象基类(如图 11-3 所示)。第 12 章接着讨论这个话题,那是重要的一章。
“PEP 3119—Introducing Abstract Base Classes”讲解了抽象基类的基本原理,“PEP 3141—A Type Hierarchy for Numbers”提出了 numbers 模块中的抽象基类。
Bill Venners 对 Guido van Rossum 的采访“Contracts in Python: A Conversation with Guido van Rossum, Part IV”讨论了动态类型的优缺点。
zope.interface 包提供了一种声明接口的方式:检查对象是否实现了接口,注册提供方,然后查询指定接口的提供方。一开始,这个包是 Zope 3 核心的一部分,不过它可以在 Zope 外部使用,而且已经有人这么做了。这个包为大型 Python 项目(如 Twisted、Pyramid 和 Plone)的组件式架构提供了灵活的基础。Lennart Regebro 写的“A Python Component Architecture”一文对 zope.interface 包做了介绍,Baiju M 还写了一本相关的书——A Comprehensive Guide to Zope Component Architecture。
杂谈
类型提示
2014 年,Python 世界最大的新闻应该是 Guido van Rossum 同意实现可选的静态类型检查,这与检查程序 Mypy 的做法类似,即使用函数注解实现。这一消息出自 8 月 15 日发表在 Python-ideas 邮件列表中的一个话题,题为“Optional static typing —the crossroads”。一个月后,“PEP 484—Type Hints”草案发布了,发起人是 Guido。
这个功能的目的是让程序员在函数定义中使用注解声明参数和返回值的类型,但这是可选的。关键在于“可选”二字。仅当你想得到注解的好处和限制时才需要添加注解,而且可以在一些函数中添加,在另一些函数中不添加。
从表面上看,这与 Microsoft 对 TypeScript(JavaScript 的超集)采取的方式类似,不过 TypeScript 做得更进一步:TypeScript 添加了新的语言结构(如模块、类、显式接口,等等),允许声明变量类型,而且最终编译成常规的 JavaScript。目前来看,Python 的可选静态类型没这么大的雄心。
为了理解这个提案的动机,不能忽略 Guido 在 2014 年 8 月 15 日发送的那封重要邮件中的这段话:
我还得做个假设:这个功能主要供 lint 程序、IDE 和文档生成工具使用。这些工具有个共同点:即使类型检查失败了,程序仍能运行。此外,程序中添加的类型不能降低性能(也不能提升性能 :-))。
因此,这一举动并不像乍一看那么激进。“PEP 484—Type Hints”提到了“PEP 482—Literature Overview for Type Hints”,后者概述了第三方 Python 工具和其他语言实现类型提示的方式。
不管激进不激进,类型提示都将到来:支持 PEP 484 的 typing 模块好像已经纳入 Python 3.5。23 根据这个提案的表述和实现方式,可以肯定的是,现有代码不会因为缺少类型提示(或相关的附加物)而无法运行。
最后,PEP 484 明确指出:
还要强调一点,Python 依旧是一门动态类型语言,作者从未打算强制要求使用类型提示,甚至不会把它变成约定。
Python 是弱类型语言吗
由于缺少统一的术语,讨论语言类型方面的话题时有时会让人不明其意。有些人(例如扩展阅读中提到的 Bill Venners 对 Guido 的访谈)说 Python 是弱类型语言,把 Python 与 JavaScript 和 PHP 归为一类。讨论类型时,最好考虑两条不同的坐标线。
强类型和弱类型
如果一门语言很少隐式转换类型,说明它是强类型语言;如果经常这么做,说明它是弱类型语言。Java、C++ 和 Python 是强类型语言。PHP、JavaScript 和 Perl 是弱类型语言。
静态类型和动态类型
在编译时检查类型的语言是静态类型语言,在运行时检查类型的语言是动态类型语言。静态类型需要声明类型(有些现代语言使用类型推导避免部分类型声明)。Fortran 和 Lisp 是最早的两门语言,现在仍在使用,它们分别是静态类型语言和动态类型语言。
强类型能及早发现缺陷。
下面几例体现了弱类型的不足:24
// 这些是JavaScript代码(在Node.js v0.10.33中做了测试) '' == '0' // false 0 == '' // true 0 == '0' // true '' < 0 // false '' < '0' // true
因为 Python 不会自动在字符串和数字之间强制转换,所以在 Python 3 中,上述 == 表达式的结果都是 False(保留了 == 的意思),而 < 比较会抛出 TypeError。
静态类型使得一些工具(编译器和 IDE)便于分析代码、找出错误和提供其他服务(优化、重构,等等)。动态类型便于代码重用,代码行数更少,而且能让接口自然成为协议而不提早实行。
综上,Python 是动态强类型语言。“PEP 484—Type Hints”无法改变这一点,但是 API 作者能够添加可选的类型注解,执行某种静态类型检查。
猴子补丁
猴子补丁的名声不太好。如果滥用,会导致系统难以理解和维护。补丁通常与目标紧密耦合,因此很脆弱。另一个问题是,打了猴子补丁的两个库可能相互牵绊,因为第二个库可能撤销了第一个库的补丁。
不过猴子补丁也有它的作用,例如可以在运行时让类实现协议。适配器设计模式通过实现全新的类解决这种问题。
为 Python 打猴子补丁不难,但是有些局限。与 Ruby 和 JavaScript 不同,Python 不允许为内置类型打猴子补丁。其实我觉得这是优点,因为这样可以确保 str 对象的方法始终是那些。这一局限能减少外部库打的补丁有冲突的概率。
Java、Go 和 Ruby 的接口
从 C++ 2.0(1989 年发布)起,这门语言开始使用抽象类指定接口。Java 的设计者选择不支持类的多重继承,这排除了使用抽象类作为接口规范的可能性,因为一个类通常会实现多个接口。但是,Java 的设计者添加了 interface 这个语言结构,而且允许一个类实现多个接口——这是一种多重继承。以更为明确的方式定义接口是 Java 的一大贡献。在 Java 8 中,接口可以提供方法实现,这叫默认方法。有了这个功能,Java 的接口与 C++ 和 Python 中的抽象类更像了。
Go 语言采用的方式完全不同。首先,Go 不支持继承。我们可以定义接口,但是无需(其实也不能)明确地指出某个类型实现了某个接口。编译器能自动判断。因此,考虑到接口在编译时检查,但是真正重要的是实现了什么类型,Go 语言可以说是具有“静态鸭子类型”。
与 Python 相比,对 Go 来说就好像每个抽象基类都实现了 __subclasshook__ 方法,它会检查函数的名称和签名,而我们自己从不需要继承或注册抽象基类。如果想让 Python 更像 Go,可以对所有函数参数做类型检查。Python 提供了部分基础设施(参见 5.9 节)。Guido 说过,他不介意使用注解做类型检查,至少在辅助工具中可以这么做。详情参阅第 5 章的“杂谈”。
Ruby 程序员是鸭子类型的坚定拥护者,而且 Ruby 没有声明接口或抽象类的正式方式,只能像 Python 2.6 之前的版本那样做,即在方法的定义体中抛出 NotImplementedError,以此表明方法是抽象的,用户必须在子类中实现。
不过,2014 年 9 月,Ruby 之父松本行弘在日本举办的 Ruby Kaigi(最重要的 Ruby 大会之一,每年举办)中做了一场主题演讲,他透露说,Ruby 未来可能会支持静态类型。目前我还没看到相关报道,但是根据 Godfrey Chan 的博客文章“Ruby Kaigi 2014: Day 2”,松本行弘关注的似乎是函数注解。他甚至还提到了 Python 的函数注解。
在没有抽象基类向类型系统添加结构,以及不丧失灵活性的情况下,我不知道函数注解有什么用。因此,Ruby 未来可能还会支持正式接口。
我相信,Python 的抽象基类在 register 函数和 __subclasshook__ 方法的协助下能把正式接口带入这门语言,而且不失去动态类型的优势。
或许,鹅正在赶超鸭子。
接口中的隐喻和习惯用法
隐喻能打破壁垒,让人更易于理解。使用“栈”和“队列”描述基本的数据类型就有这样的功效:这两个词清楚地道出了添加或删除元素的方式。另一方面,Alan Cooper 在《交互设计精髓(第 4 版)》中写道:
严格奉行隐喻设计毫无必要,却把界面死死地与物理世界的运行机制捆绑在一起。
他说的是用户界面,但对 API 同样适用。不过 Cooper 同意,当“真正合适的”隐喻“正中下怀”时,可以使用隐喻(他用的词是“正中下怀”,因为合适的隐喻可遇不可求)。我觉得本章用宾果机作比喻是合适的,我相信自己。
我读过不少 UI 设计方面的书,《交互设计精髓》是最好的。我从 Cooper 的书中学到的最宝贵的知识是,不把隐喻当作设计范式,而代之以“习惯用法的界面”。前面说过,Cooper 说的不是 API,但是我越深入思考他的观点,越觉得可以将之运用到 Python 中。Python 语言的基本协议就是 Cooper 所说的“习惯用法”。知道“序列”是什么之后,可以把这些知识应用到不同的场合。这正是本书的主要目的:着重讲解这门语言的基本惯用法,让你的代码简洁、高效且可读,把你打造成熟练的 Python 程序员。
23现在,typing 模块已经纳入 Python 3.5。——编者注
24改编自 JavaScript: The Good Parts(Douglas Crockford 著)附录 B 第 109 页给出的示例。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论