Python for循环中的陷阱详解


Posted in Python onJuly 13, 2018

前言

Python 中的 for 循环和其他语言中的 for 循环工作方式是不一样的,今天就带你深入了解 Python 的 for 循环,看看它是如何工作的,以及它为什么按照这种方式工作。

循环中的陷阱

我们先来看一下 Python 循环中的「陷阱」,在我们了解了循环的工作方式后,再来看下这些陷阱到底是怎么出现的。

陷阱 1:循环两次

现在我们先假设有一个数字组成的列表,和一个用于返回这些数字的平方的生成器:

>>> nums = [1, 2, 3, 4]
>>> squares = (n**2 for n in nums)

我们可以将这个生成器对象传递给元组构造器,从而可以得到一个元组:

>>> tuple(squares)
(1, 4, 9, 16)

这个时候,如果我们再将这个构造器对象传递给 sum 函数,按理说应该会返回这些数字的和吧:

>>> sum(squares)
0

返回的是个 0,先拖住下巴。

陷阱 2:检查是否包含

我们还是使用上面的数字列表和生成器:

>>> nums = [1, 2, 3, 4]
>>> squares = (n**2 for n in nums)

如果我 squares 生成器中是否包含 9,答案是肯定的,若果我再问一次呢?

Python for循环中的陷阱详解

你敢答应吗

>>> 9 in squares
True
>>> 9 in squares
False

发现,第二次不灵了~

怎么不灵了

陷阱 3:拆包

现在假设有一个字典:

>>> counts = {1:'a', 2:'b'}

然后,我们用多个变量对字典进行拆包:

>>> x,y = counts

你觉得这时候,x 和 y 中会是什么?

>>> x
1
>>> y
2

我们只得到了键。

下面,我们先来了解下 Python 中的循环工作原理,然后再反过头来看这些陷阱问题。

一些概念

首先,先了解一些基本概念:

可迭代和序列

可迭代就是指任意可以使用 for 循环遍历的东西,可迭代意味着可以遍历,任何可以遍历的东西都是可迭代的。

for item in some_iterable:
 print(item)

序列是一种常见的可迭代类型,如列表、元组、字符串等。

序列是可迭代的,它有着一些特点,它们是从 0 开始索引,索引长度不超过序列的长度;它们有序列长度;并且它们可以被切分。

Python 中的大部分东西都是可以迭代的,但是可以迭代并不意味着它是序列。如集合、字典、文件和生成器都是可迭代的,但是它们都不是序列。

>>> my_set = {1, 2, 3}
>>> my_dict = {'k1': 'v1', 'k2': 'v2'}
>>> my_file = open('some_file.txt')
>>> squares = (n**2 for n in my_set)

总结下来就是,任何可以用 for 循环遍历的东西都是可迭代的,序列可迭代的类型中的一种,Python 还有着许多其他种类的可迭代类型。

迭代器

迭代器就是可以驱动可迭代对象的东西。你可以从任何可迭代对象中获得迭代器,你也可以使用迭代器来手动对它的迭代进行遍历。

下面有三个可迭代对象:一个集合、一个元祖和一个字符串:

>>> nums = {1,2,3,4}
>>> coors = (4,5,6)
>>> words = "hello hoxis"

我们可以使用 Python 的内置函数 iter ,从这些可迭代对象中获取到迭代器:

>>> iter(nums)
<setiterator object at 0x7fa8c194ad70>
>>> iter(coors)
<tupleiterator object at 0x7fa8c1959610>
>>> iter(words)
<iterator object at 0x7fa8c19595d0>

一旦我们有了迭代器,我们就可以使用其内置函数  next() 来获取它的下一个值:

>>> nums = {1,2,3,4}
>>> num_iter = iter(nums)
>>> next(num_iter)
1
>>> next(num_iter)
2
>>> next(num_iter)
3
>>> next(num_iter)
4
>>> next(num_iter)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration

若果迭代到头了,也就是没有下一个值了,就会抛出 StopIteration 异常。也就是说,它不会继续循环取获取第一个值。

是不是有点懵逼了?

  • 可迭代对象是可以迭代的东西
  • 迭代对象器实际上是遍历可迭代对象的代理
  • 迭代器没有长度,它们不能被索引。
  • 可以使用迭代器来做的唯一有用的事情是将其传递给内置的 next 函数,或者对其进行循环遍历
  • 可以使用 list() 函数将迭代器转换为列表
>>> nums = {1,2,3,4}
>>> num_iter = iter(nums)
>>> next(num_iter)
1
>>> list(num_iter)
[2, 3, 4]
>>> list(num_iter)
[]

若果想再次将其转换为列表,明显地,得到的是一个空列表。

其实这也是迭代器的一个重要特性:惰性,只能使用一次,只能循环遍历一次。并且,在我们调用 next() 函数之前,它不会做任何事情。因此,我们可以创建无限长的迭代器,而创建无限长的列表则不行,那样会耗尽你的内存!

可迭代对象不一定是迭代器,但是迭代器一定是可迭代的:

对象 可迭代? 迭代器?
可迭代对象 不一定
迭代器
生成器
列表 ×

其实,Python 中有许多迭代器,生成器是迭代器,Python 的许多内置类型也是迭代器。例如,Python 的 enumerate 和 reversed 对象就是迭代器。zip, map 和 filter 也是迭代器;文件对象也是迭代器。

Python 中的 for 循环

其实,Python 并没有传统的 for 循环,什么是传统的 for 循环?

我们看下 Java 中的 for 循环:

int[] integers = {1, 2, 3, 4};
for (int j = 0; j<integers.length; j++) {
 int i = integers[j];
 System.out.println(i);
}

这是一种 C风格 的 for 循环,JavaScript、C、C++、Java、PHP 和一大堆其他编程语言都有这种风格的 for 循环,但是 Python 确实没有。

Python 中的我们称之为 for 循环的东西,确切的说应该是 foreach 循环:

numbers = [1, 2, 3, 5, 7]
for n in numbers:
 print(n)

和 C风格 的 for 循环不同之处在于,Python 的 for 循环没有索引变量,没有索引变量的初始化,边界检查和索引变量的增长。

这就是 Python 的 for 循环的不同之处!

使用索引?

你可能会怀疑,Python 的 for 循环是否在底层使用了索引,下面我们手动的使用 while 循环和索引来遍历:

>>> nums = [1,2,3,4]
>>> i = 0
>>> while i < len(nums):
...  print(num[i])
...  i += 1
...
0
1
2
3

对于列表,这样遍历是可以的,但不代表适用于所有可迭代对象,它只适用于序列。

比如,我们对一个 set 使用这种方法遍历,会得到一个异常:

>>> set = {1,2,3}
>>> i = 0
>>> while i < len(set):
...  print(set[i])
...  i += 1
...
Traceback (most recent call last):
 File "<stdin>", line 2, in <module>
TypeError: 'set' object does not support indexing

因为 set 不是序列,因此不支持索引遍历。

我们不能使用索引手动对 Python 中的每一个迭代对象进行遍历。对于那些不是序列的迭代器来说,更是行不通的。

实现没有 for 的循环

从上文可以看出,Python 中的 for 循环不使用索引,它使用的是迭代器。让我们来看下它是如何工作的。

通过上文,我们了解到了迭代器和 iter、next 函数,现在我们可以尝试不用 for 循环来遍历一个可迭代对象。

下面是一个正常的 for 循环:

def funky_for_loop(iterable, action_to_do):
 for item in iterable:
  action_to_do(item)

我们要尝试用迭代器的方法和 while 实现上面 for 循环的逻辑,大致步骤如下:

  • 获取给定可迭代对象的迭代器;
  • 调用迭代器的 next() 方法获取下一项;
  • 对当前项数据进行处理;
  • 如果捕获到 StopIteration ,那么就停止循环
def funky_for_loop(iterable, action_to_do):
 iterator = iter(iterable)
 while not done_looping:
  try:
   item = next(iterator)
  except StopIteration:
   break
  else:
   action_to_do(item)

Python 底层的循环工作方式基本上如上代码,就是迭代器驱动的 for 循环。

再次回到循环陷阱

陷阱 1:耗尽的迭代器

陷阱 1 中,因为生成器是迭代器,迭代器是惰性的,也是一次性的,在已经遍历过一次的情况下,再对其求和,返回的就是一个 0。

陷阱 2:部分消耗迭代器

陷阱 2 中,我们两次询问 9 是否存在于同一个生成器中,得到了不同的答案。

这是因为,第一次询问时,Python 已经对这个生成器进行了遍历,也就是调用 next() 函数查找 9,找到后就会返回 True,第二次再询问 9 是否存在时,会从上次的位置继续 next() 查找。

>>> nums = [1,2,3,4,5]
>>> squares = (n**2 for n in nums)
>>> 9 in squares
True
# 此时打印出来
>>> list(squares)
[16, 25]

陷阱 3:拆包是迭代

当直接在字典上迭代时,得到的是键:

>>> counts = {1:'a',2:'b'}
>>> for i in counts:
...  print(i)
... 
1
2

而对字典拆包时,和在字典上遍历是一样的,都是依赖于迭代器协议,因此得到的也是键。

总结

序列是迭代器,但是不是所有的迭代器都是序列。迭代器不可以被循环遍历两次、不能访问其长度,也不能使用索引。

迭代器是 Python 中最基本的可迭代形式。如果你想在代码中做一个惰性迭代,请考虑迭代器,并考虑使用生成器函数或生成器表达式。

最后,请记住,Python 中的每一种迭代都依赖于迭代器协议,因此理解迭代器协议是理解 Python 中的循环的关键。

原文链接:https://opensource.com/article/18/3/loop-better-deeper-look-iteration-python

好了以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
python端口扫描系统实现方法
Nov 19 Python
python魔法方法-属性转换和类的表示详解
Jul 22 Python
Python使用openpyxl读写excel文件的方法
Jun 30 Python
通过Python实现自动填写调查问卷
Sep 06 Python
浅谈python 里面的单下划线与双下划线的区别
Dec 01 Python
pandas 转换成行列表进行读取与Nan处理的方法
Oct 30 Python
pygame游戏之旅 添加碰撞效果的方法
Nov 20 Python
Python调用服务接口的实例
Jan 03 Python
Python面向对象类编写细节分析【类,方法,继承,超类,接口等】
Jan 05 Python
Python类的继承用法示例
Jan 31 Python
基于python实现生成指定大小txt文档
Jul 20 Python
Python爬虫入门案例之回车桌面壁纸网美女图片采集
Oct 16 Python
Python框架Flask的基本数据库操作方法分析
Jul 13 #Python
Python使用pickle模块实现序列化功能示例
Jul 13 #Python
mac下如何将python2.7改为python3
Jul 13 #Python
Python面向对象程序设计之继承与多继承用法分析
Jul 13 #Python
Python2.7环境Flask框架安装简明教程【已测试】
Jul 13 #Python
解决Python print 输出文本显示 gbk 编码错误问题
Jul 13 #Python
Python爬虫实现简单的爬取有道翻译功能示例
Jul 13 #Python
You might like
java EJB 加密与解密原理的一个例子
2008/01/11 PHP
php冒泡排序、快速排序、快速查找、二维数组去重实例分享
2014/04/24 PHP
PHP判断一个数组是另一个数组子集的方法详解
2017/07/31 PHP
Mootools 1.2教程 同时进行多个形变动画
2009/09/15 Javascript
Javascript的各种节点操作实例演示代码
2012/06/27 Javascript
jQuery+easyui中的combobox实现下拉框特效
2015/02/27 Javascript
jQuery事件绑定on()、bind()与delegate() 方法详解
2015/06/03 Javascript
js判断当前页面用什么浏览器打开的方法
2016/01/06 Javascript
jquery获取img的src值的简单实例
2016/05/17 Javascript
JS实现关闭当前页而不弹出提示框的方法
2016/06/22 Javascript
js中window.open的参数及注意注意事项
2016/07/06 Javascript
简单谈谈Vue 模板各类数据绑定
2016/09/25 Javascript
jQuery表格的维护和删除操作
2017/02/03 Javascript
详解Vue 非父子组件通信方法(非Vuex)
2017/05/24 Javascript
vue中配置mint-ui报css错误问题的解决方法
2017/10/11 Javascript
(模仿京东用户注册)用JQuery实现简单表单验证,初学者必看
2018/01/08 jQuery
微信小程序实现通过双向滑动缩放图片大小的方法
2018/12/30 Javascript
NVM安装nodejs的方法实用步骤
2019/01/16 NodeJs
如何配置vue.config.js 处理static文件夹下的静态文件
2020/06/19 Javascript
解决vue路由name同名,路由重复的问题
2020/08/05 Javascript
python实现从字符串中找出字符1的位置以及个数的方法
2014/08/25 Python
Python内置的HTTP协议服务器SimpleHTTPServer使用指南
2016/03/30 Python
Python读取文件内容的三种常用方式及效率比较
2017/10/07 Python
Python 将RGB图像转换为Pytho灰度图像的实例
2017/11/14 Python
Python中的pygal安装和绘制直方图代码分享
2017/12/08 Python
python中将正则过滤的内容输出写入到文件中的实例
2018/10/21 Python
在python环境下运用kafka对数据进行实时传输的方法
2018/12/27 Python
Pandas时间序列:时期(period)及其算术运算详解
2020/02/25 Python
HTML5 Canvas实现图片缩放、翻转、颜色渐变的代码示例
2016/02/28 HTML / CSS
世界最大的私人旅行指南出版商:孤独星球
2016/08/23 全球购物
syb养殖创业计划书
2014/01/09 职场文书
搞笑爱情保证书
2014/04/29 职场文书
学校师德师风自我剖析材料
2014/09/29 职场文书
初中生散播谣言检讨书
2014/11/17 职场文书
Redis调用Lua脚本及使用场景快速掌握
2022/03/16 Redis
Apache Hudi集成Spark SQL操作hide表
2022/03/31 Servers