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编程中的反模式实例分析
Dec 08 Python
分析用Python脚本关闭文件操作的机制
Jun 28 Python
Python SQLite3数据库日期与时间常见函数用法分析
Aug 14 Python
Python验证文件是否可读写代码分享
Dec 11 Python
Python基于OpenCV实现视频的人脸检测
Jan 23 Python
解决Python下imread,imwrite不支持中文的问题
Dec 05 Python
python实现简单图片物体标注工具
Mar 18 Python
flask应用部署到服务器的方法
Jul 12 Python
python+selenium 点击单选框-radio的实现方法
Sep 03 Python
关于Python字符串显示u...的解决方式
Mar 06 Python
Win10下用Anaconda安装TensorFlow(图文教程)
Jun 18 Python
如何使用Django Admin管理后台导入CSV
Nov 06 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
PHP将回调函数作用到给定数组单元的方法
2014/08/19 PHP
php简单实现短网址(短链)还原的方法(测试可用)
2016/05/09 PHP
php7基于递归实现删除空文件夹的方法示例
2017/06/15 PHP
PHP实现函数内修改外部变量值的方法示例
2018/12/28 PHP
PHP 判断字符串是中文还是英文, 或者是中英混合
2021/03/09 PHP
JS+CSS3模拟溢出滚动效果
2016/08/12 Javascript
简单实现Vue的observer和watcher
2016/12/21 Javascript
jQuery实现获取h1-h6标题元素值的方法
2017/03/06 Javascript
jQuery插件zTree实现的基本树与节点获取操作示例
2017/03/08 Javascript
浅谈angular.copy() 深拷贝
2017/09/14 Javascript
微信小程序实现发红包功能
2018/07/11 Javascript
详解Webpack-dev-server的proxy用法
2018/09/08 Javascript
微信小程序如何获取手机验证码
2018/11/04 Javascript
微信小程序实现滑动切换自定义页码的方法分析
2018/12/29 Javascript
在Create React App中使用CSS Modules的方法示例
2019/01/15 Javascript
Vue项目实现换肤功能的一种方案分析
2019/08/28 Javascript
Nodejs封装类似express框架的路由实例详解
2020/01/05 NodeJs
vue实现列表滚动的过渡动画
2020/06/29 Javascript
vue props default Array或是Object的正确写法说明
2020/07/30 Javascript
Openlayers学习之加载鹰眼控件
2020/09/28 Javascript
[01:05:12]2014 DOTA2国际邀请赛中国区预选赛 TongFu VS CIS-GAME
2014/05/21 DOTA
python检测空间储存剩余大小和指定文件夹内存占用的实例
2018/06/11 Python
浅谈Python中的全局锁(GIL)问题
2019/01/11 Python
python 中如何获取列表的索引
2019/07/02 Python
Python替换月份为英文缩写的实现方法
2019/07/15 Python
Python autoescape标签用法解析
2020/01/17 Python
解决tensorboard多个events文件显示紊乱的问题
2020/02/15 Python
python实现AdaBoost算法的示例
2020/10/03 Python
最新版 Windows10上安装Python 3.8.5的步骤详解
2020/11/28 Python
python接口自动化框架实战
2020/12/23 Python
什么是smarty? Smarty的优点是什么?
2013/08/11 面试题
J2ee常用的设计模式?说明工厂模式
2015/05/21 面试题
化工专业自荐书
2014/06/16 职场文书
地震捐款倡议书
2014/08/29 职场文书
大学生作弊检讨书
2014/09/11 职场文书
安全检查汇报材料
2014/12/26 职场文书