前言

这是 “Python 工匠”系列的第 7 篇文章。(点击原文链接,可查看系列其他文章)


循环是一种常用的程序控制结构。我们常说,机器相比人类的最大优点之一,就是机器可以不眠不休的重复做某件事情,但人却不行。而“循环”,则是实现让机器不断重复工作的关键概念。

在循环语法方面,Python 表现的即传统又不传统。它虽然抛弃了常见的 for(init;condition;incrment) 三段式结构,但还是选择了 for 和 while 这两个经典的关键字来表达循环。绝大多数情况下,我们的循环需求都可以用 for<item>in<iterable> 来满足, while<condition> 相比之下用的则更少些。

虽然循环的语法很简单,但是要写好它确并不容易。在这篇文章里,我们将探讨什么是“地道”的循环代码,以及如何编写它们。


什么是“地道”的循环?

“地道”这个词,通常被用来形容某人做某件事情时,非常符合当地传统,做的非常好。打个比方,你去参加一个朋友聚会,同桌的有一位广东人,对方一开口,句句都是标准京腔、完美儿化音。那你可以对她说:“您的北京话说的真地道”。

既然“地道”这个词形容的经常是口音、做菜的口味这类实实在在的东西,那“地道”的循环代码又是什么意思呢?让我拿一个经典的例子来解释一下。

如果你去问一位刚学习 Python 一个月的人:“如何在遍历一个列表的同时获取当前下标?”。他可能会交出这样的代码:


上面的循环虽然没错,但它确一点都不“地道”。一个拥有三年 Python 开发经验的人会说,代码应该这么写:


enumerate() 是 Python 的一个内置函数,它接收一个“可迭代”对象作为参数,然后返回一个不断生成 (当前下标,当前元素) 的新可迭代对象。这个场景使用它最适合不过。

所以,在上面的例子里,我们会认为第二段循环代码比第一段更“地道”。因为它用更直观的代码,更聪明的完成了工作。

enumerate() 所代表的编程思路

不过,判断某段循环代码是否地道,并不仅仅是以知道或不知道某个内置方法作为标准。我们可以从上面的例子挖掘出更深层的东西。

如你所见,Python 的 for 循环只有 for<item>in<iterable> 这一种结构,而结构里的前半部分 - 赋值给 item- 没有太多花样可玩。所以后半部分的 可迭代对象 是我们唯一能够大做文章的东西。而以 enumerate() 函数为代表的“修饰函数”,刚好提供了一种思路:通过修饰可迭代对象来优化循环本身。

这就引出了我的第一个建议。


建议1:使用函数修饰被迭代对象来优化循环

使用修饰函数处理可迭代对象,可以在各种方面影响循环代码。而要找到合适的例子来演示这个方法,并不用去太远,内置模块 itertools 就是一个绝佳的例子。

简单来说,itertools 是一个包含很多面向可迭代对象的工具函数集。我在之前的系列文章《容器的门道》里提到过它。

如果要学习 itertools,那么 Python 官方文档 是你的首选,里面有非常详细的模块相关资料。但在这篇文章里,侧重点将和官方文档稍有不同。我会通过一些常见的代码场景,来详细解释它是如何改善循环代码的。

1. 使用 product 扁平化多层嵌套循环

虽然我们都知道“扁平的代码比嵌套的好”。但有时针对某类需求,似乎一定得写多层嵌套循环才行。比如下面这段:

对于这种需要嵌套遍历多个对象的多层循环代码,我们可以使用 product() 函数来优化它。product() 可以接收多个可迭代对象,然后根据它们的笛卡尔积不断生成结果。

相比之前的代码,使用 product() 的函数只用了一层 for 循环就完成了任务,代码变得更精炼了。

2. 使用 islice 实现循环内隔行处理

有一份包含 Reddit 帖子标题的外部数据文件,里面的内容格式是这样的:

可能是为了美观,在这份文件里的每两个标题之间,都有一个 “—” 分隔符。现在,我们需要获取文件里所有的标题列表,所以在遍历文件内容的过程中,必须跳过这些无意义的分隔符。

参考之前对 enumerate() 函数的了解,我们可以通过在循环内加一段基于当前循环序号的 if 判断来做到这一点:

但对于这类在循环内进行隔行处理的需求来说,如果使用 itertools 里的 islice() 函数修饰被循环对象,可以让循环体代码变得更简单直接。

islice(seq,start,end,step) 函数和数组切片操作( list[start:stop:step] )有着几乎一模一样的参数。如果需要在循环内部进行隔行处理的话,只要设置第三个递进步长参数 step 值为 2 即可(默认为 1)。

3. 使用 takewhile 替代 break 语句

有时,我们需要在每次循环开始时,判断循环是否需要提前结束。比如下面这样:

对于这类需要提前中断的循环,我们可以使用 takewhile() 函数来简化它。takewhile(predicate,iterable)会在迭代 iterable 的过程中不断使用当前对象作为参数调用 predicate 函数并测试返回结果,如果函数返回值为真,则生成当前对象,循环继续。否则立即中断当前循环。

使用 takewhile 的代码样例:

itertools 里面还有一些其他有意思的工具函数,他们都可以用来和循环搭配使用,比如使用 chain 函数扁平化双层嵌套循环、使用 zip_longest 函数一次同时循环多个对象等等。

篇幅有限,我在这里不再一一介绍。如果有兴趣,可以自行去官方文档详细了解。

4. 使用生成器编写自己的修饰函数

除了 itertools 提供的那些函数外,我们还可以非常方便的使用生成器来定义自己的循环修饰函数。

让我们拿一个简单的函数举例:


在上面的函数里,循环体内为了过滤掉所有奇数,引入了一条额外的 if 判断语句。如果要简化循环体内容,我们可以定义一个生成器函数来专门进行偶数过滤:

numbers 变量使用 even_only 函数装饰后, sum_even_only_v2 函数内部便不用继续关注“偶数过滤”逻辑了,只需要简单完成求和即可。

Hint:当然,上面的这个函数其实并不实用。在现实世界里,这种简单需求最适合直接用生成器/列表表达式搞定:sum(numfornuminnumbersifnum%2==0)


建议2:按职责拆解循环体内复杂代码块

我一直觉得循环是一个比较神奇的东西,每当你写下一个新的循环代码块,就好像开辟了一片黑魔法阵,阵内的所有内容都会开始无休止的重复执行。

但我同时发现,这片黑魔法阵除了能带来好处,它还会引诱你不断往阵内塞入越来越多的代码,包括过滤掉无效元素、预处理数据、打印日志等等。甚至一些原本不属于同一抽象的内容,也会被塞入到同一片黑魔法阵内

你可能会觉得这一切理所当然,我们就是迫切需要阵内的魔法效果。如果不把这一大堆逻辑塞满到循环体内,还能把它们放哪去呢?

让我们来看看下面这个业务场景。在网站中,有一个每 30 天执行一次的周期脚本,它的任务是是查询过去 30 天内,在每周末特定时间段登录过的用户,然后为其发送奖励积分。

代码如下:

上面这个函数主要由两层循环构成。外层循环的职责,主要是获取过去 30 天内符合要求的时间,并将其转换为 UNIX 时间戳。之后由内层循环使用这两个时间戳进行积分发送。

如之前所说,外层循环所开辟的黑魔法阵内被塞的满满当当。但通过观察后,我们可以发现 整个循环体其实是由两个完全无关的任务构成的:“挑选日期与准备时间戳” 以及 “发送奖励积分”

复杂循环体如何应对新需求

这样的代码有什么坏处呢?让我来告诉你。

某日,产品找过来说,有一些用户周末半夜不睡觉,还在刷我们的网站,我们得给他们发通知让他们以后早点睡觉。于是新需求出现了:“给过去 30 天内在周末凌晨 3 点到 5 点登录过的用户发送一条通知”。

新问题也随之而来。敏锐如你,肯定一眼可以发现,这个新需求在用户筛选部分的要求,和之前的需求非常非常相似。但是,如果你再打开之前那团循环体看看,你会发现代码根本没法复用,因为在循环内部,不同的逻辑完全被 耦合 在一起了。☹️

在计算机的世界里,我们经常用 “耦合” 这个词来表示事物之间的关联关系。上面的例子中,“挑选时间”和“发送积分”这两件事情身处同一个循环体内,建立了非常强的耦合关系。

为了更好的进行代码复用,我们需要把函数里的“挑选时间”部分从循环体中解耦出来。而我们的老朋友,“生成器函数” 是进行这项工作的不二之选。

使用生成器函数解耦循环体

要把 “挑选时间” 部分从循环内解耦出来,我们需要定义新的生成器函数 gen_weekend_ts_ranges(),专门用来生成需要的 UNIX 时间戳:

有了这个生成器函数后,旧需求“发送奖励积分”和新需求“发送通知”,就都可以在循环体内复用它来完成任务了:


总结

在这篇文章里,我们首先简单解释了“地道”循环代码的定义。然后提出了第一个建议:使用修饰函数来改善循环。之后我虚拟了一个业务场景,描述了按职责拆解循环内代码的重要性。

一些要点总结:

  • 使用函数修饰被循环对象本身,可以改善循环体内的代码
  • itertools 里面有很多工具函数都可以用来改善循环
  • 使用生成器函数可以轻松定义自己的修饰函数
  • 循环内部,是一个极易发生“代码膨胀”的场地
  • 请使用生成器函数将循环内不同职责的代码块解耦出来,获得更好的灵活性

看完文章的你,有没有什么想吐槽的?请留言或者在 项目 Github Issues 告诉我吧。


附录

题图来源: Photo by Lai man nung on Unsplash
更多系列文章地址:https://github.com/piglei/one-python-craftsman

系列其他文章:

Python 工匠:让函数返回结果的技巧
Python 工匠:异常处理的三个好习惯


蓝鲸智云

本文由腾讯蓝鲸智云编辑发布,腾讯蓝鲸智云(简称蓝鲸)软件体系是一套基于PaaS的技术解决方案,致力于打造行业领先的一站式自动化运维平台。目前已经推出社区版、企业版,欢迎体验。

  • 官网:https://bk.tencent.com/
  • 下载链接:https://bk.tencent.com/download/
  • 社区:https://bk.tencent.com/s-mart/community/question

Python 工匠:编写地道循环的两个建议相关推荐

  1. Python编写循环的两个建议 | 鹅厂实战

    作者 | piglei(腾讯高级工程师) 转载自腾讯技术工程知乎专栏 循环是一种常用的程序控制结构.我们常说,机器相比人类的最大优点之一,就是机器可以不眠不休的重复做某件事情,但人却不行.而" ...

  2. python两层循环 循环完整体一个_Python学习教程(Python学习路线):Python编写循环的两个建议...

    原标题:Python学习教程(Python学习路线):Python编写循环的两个建议 Python学习教程(Python学习路线):给大家总结了两个Python编写循环的建议 循环是一种常用的程序控制 ...

  3. python画魔法阵_Python编写循环的两个建议 | 鹅厂实战!

    本文系 "Python 工匠"系列的第 7 篇文章,已取得作者授权. 循环是一种常用的程序控制结构.我们常说,机器相比人类的最大优点之一,就是机器可以不眠不休的重复做某件事情,但人 ...

  4. python如何实现隔行_Python编写循环的两个建议 | 鹅厂实战

    作者 | piglei(腾讯高级工程师) 转载自腾讯技术工程知乎专栏 循环是一种常用的程序控制结构.我们常说,机器相比人类的最大优点之一,就是机器可以不眠不休的重复做某件事情,但人却不行.而" ...

  5. python两层循环 循环完整体一个_Python编写循环的两个建议 | 鹅厂实战(文末有福利)...

    循环是一种常用的程序控制结构.我们常说,机器相比人类的最大优点之一,就是机器可以不眠不休的重复做某件事情,但人却不行.而**"循环"**,则是实现让机器不断重复工作的关键概念. 在 ...

  6. Python 工匠精神

    个人git 地址 :   https://github.com/xxguo 我一直觉得编程某种意义上是一门"手艺",因为优雅而高效的代码,就如同完美的手工艺品一样让人赏心悦目. 在 ...

  7. Python 工匠:编写条件分支代码的技巧

    『Python 工匠』是什么? 我一直觉得编程某种意义上是一门『手艺』,因为优雅而高效的代码,就如同完美的手工艺品一样让人赏心悦目. 在雕琢代码的过程中,有大工程:比如应该用什么架构.哪种设计模式.也 ...

  8. 编写python程序、利用循环输出_Python基础编程—用户输入和while循环

    温馨提示 如果你喜欢本文,请分享到朋友圈,想要获得更多信息,请关注我. 函数input()的工作原理 函数input()让程序暂停运行,等待用户输入一些文本.获取用户输入后,Python将其存储在一个 ...

  9. python条件循环叠加_Python基础:条件判断与循环的两个要点

    一.条件判断: Python中,条件判断用if语句实现,多个条件判断时用if...elif实现:看下面一段程序 #python 3.3.5#test if...elif age = 20 if age ...

最新文章

  1. 动手扩充FreeTextBox的功能
  2. 2010-11季,关注波士顿凯尔特人的10个理由
  3. html不支持png%3e,关于Adobe Flash CS3无法导入png格式图片的修复方法(真正解决问题)...
  4. 分布式缓存redis 方案_Redis缓存和MySQL数据一致性方案详解
  5. cmd下的一些小技巧
  6. 2015 CCPC 这次,我为自己鼓掌
  7. Notepad++的SQL格式化插件
  8. 计算机视觉入门大全:基础概念、运行原理、应用案例详解
  9. MCU之芯唐ARM9(转)
  10. 抖音壁纸小程序搭建教程
  11. Pixhawk系列飞控状态通知
  12. 美元用计算机怎么算,美金人民币汇率换算(人民币兑换率计算器)
  13. 南传法句经(摘选)03
  14. Word2016“此功能看似已中断 并需要修复”问题解决办法
  15. 如何将汉字写得又好又快
  16. [深入理解Android卷二 全文-第五章]深入理解PowerManagerService
  17. 老毛桃重启计算机没反应,遇到电脑无法启动时 老毛桃Win10如何原因分析解决
  18. 一晚上写出来的游戏 苹果也会推荐!
  19. 计算机机器人游戏教学计划,机器人教学计划.docx
  20. 搭搭云 新世代超级应用平台 JNPF快速开发平台框架 3.4.7最新版源码

热门文章

  1. 生产跟踪是生产控制的基础,其主要功能有哪些?
  2. 2022年高处安装、维护、拆除考试练习题及模拟考试
  3. HCNP——LSA头部
  4. 夕阳的余辉却把近山显露得清清楚楚
  5. jquery表格日历写入html,jQuery简单实现日历的方法
  6. 小程序在wxml中使用js,wxs,取整,判断等操作
  7. IDEA设置注释作者名字,模板创建,代码快捷键设置
  8. 黑马程序员——基础学习感悟总结...
  9. games202:六,实时光线追踪RTRT:Temporal Filtering、联合双边滤波、Outlier Removal、SVGF、RAE
  10. git merge命令