- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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 术语表
- 作者简介
- 关于封面
15.1 先做这个,再做那个:if 语句之外的 else 块
这个语言特性不是什么秘密,但却没有得到重视:else 子句不仅能在 if 语句中使用,还能在 for、while 和 try 语句中使用。
for/else、while/else 和 try/else 的语义关系紧密,不过与 if/else 差别很大。起初,else 这个单词的意思阻碍了我对这些特性的理解,但是最终我习惯了。
else 子句的行为如下。
for
仅当 for 循环运行完毕时(即 for 循环没有被 break 语句中止)才运行 else 块。
while
仅当 while 循环因为条件为假值而退出时(即 while 循环没有被 break 语句中止)才运行 else 块。
try
仅当 try 块中没有异常抛出时才运行 else 块。官方文档还指出:“else 子句抛出的异常不会由前面的 except 子句处理。”
在所有情况下,如果异常或者 return、break 或 continue 语句导致控制权跳到了复合语句的主块之外,else 子句也会被跳过。
我觉得除了 if 语句之外,其他语句选择使用 else 关键字是个错误。else 蕴含着“排他性”这层意思,例如“要么运行这个循环,要么做那件事”。可是,在循环中,else 的语义恰好相反:“运行这个循环,然后做那件事。”因此,使用 then 关键字更好。then 在 try 语句的上下文中也说得通:“尝试运行这个,然后做那件事。”可是,添加新关键字属于语言的重大变化,而 Guido 唯恐避之不及。
在这些语句中使用 else 子句通常能让代码更易于阅读,而且能省去一些麻烦,不用设置控制标志或者添加额外的 if 语句。
在循环中使用 else 子句的方式如下述代码片段所示:
for item in my_list: if item.flavor == 'banana': break else: raise ValueError('No banana flavor found!')
一开始,你可能觉得没必要在 try/except 块中使用 else 子句。毕竟,在下述代码片段中,只有 dangerous_call() 不抛出异常,after_call() 才会执行,对吧?
try: dangerous_call() after_call() except OSError: log('OSError...')
然而,after_call() 不应该放在 try 块中。为了清晰和准确,try 块中应该只抛出预期异常的语句。因此,像下面这样写更好:
try: dangerous_call() except OSError: log('OSError...') else: after_call()
现在很明确,try 块防守的是 dangerous_call() 可能出现的错误,而不是 after_call()。而且很明显,只有 try 块不抛出异常,才会执行 after_call()。
在 Python 中,try/except 不仅用于处理错误,还常用于控制流程。为此,Python 官方词汇表还定义了一个缩略词(口号)。
EAFP
取得原谅比获得许可容易(easier to ask for forgiveness than permission)。这是一种常见的 Python 编程风格,先假定存在有效的键或属性,如果假定不成立,那么捕获异常。这种风格简单明快,特点是代码中有很多 try 和 except 语句。与其他很多语言一样(如 C 语言),这种风格的对立面是 LBYL 风格。
接下来,词汇表定义了 LBYL。
LBYL
三思而后行(look before you leap)。这种编程风格在调用函数或查找属性或键之前显式测试前提条件。与 EAFP 风格相反,这种风格的特点是代码中有很多 if 语句。在多线程环境中,LBYL 风格可能会在“检查”和“行事”的空当引入条件竞争。例如,对 if key in mapping: return mapping[key] 这段代码来说,如果在测试之后,但在查找之前,另一个线程从映射中删除了那个键,那么这段代码就会失败。这个问题可以使用锁或者 EAFP 风格解决。
如果选择使用 EAFP 风格,那就要更深入地了解 else 子句,并在 try/except 语句中合理使用。
下面探讨本章的主要话题:强大的 with 语句。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论