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,答案是肯定的,若果我再问一次呢?

你敢答应吗

>>> 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 的 enumeratereversed 对象就是迭代器。zip, mapfilter 也是迭代器;文件对象也是迭代器。

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 循环的逻辑,大致步骤如下:

  1. 获取给定可迭代对象的迭代器;
  2. 调用迭代器的 next() 方法获取下一项;
  3. 对当前项数据进行处理;
  4. 如果捕获到 StopIteration ,那么就停止循环
def funky_for_loop(iterable, action_to_do):iterator = iter(iterable)while not done_looping:try:item = next(iterator)except StopIteration:breakelse: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

转载于:https://www.cnblogs.com/mountain2011/p/11427159.html

Python 循环中的陷阱(转载)相关推荐

  1. Python 循环中的陷阱

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

  2. python循环 break举例,python循环中使用break语句终止循环

    break语句是用来 终止 循环语句的,即哪怕循环条件没有称为False或序列还没有被完全递归,也停止执行循环语句. 一个重要的注释是,如果你从for或while循环中 终止 ,任何对应的循环else ...

  3. Python循环中的变量作用域的灵异现象

    写在前面的画 最近很想看<名侦探柯南>,但是没有时间看啊.时间实在是太少了,吃的不好睡的不好,只好写篇博客,取的名字就像名侦探柯南盗版破案名字山寨版. 案件描述 事情是这样的,今天写代码, ...

  4. Python循环结构详解

    今天继续给大家介绍Python相关知识,本文主要内容是Python循环结构. 循环是一种编程语言的重要结构,在Python中,存在着两种循环,一种是遍历循环,一种是while循环. 一.遍历循环 所谓 ...

  5. 关于Python中的yield(转载)

    您可能听说过,带有 yield 的函数在 Python 中被称之为 generator(生成器),何谓 generator ? 我们先抛开 generator,以一个常见的编程题目来展示 yield ...

  6. python循环语句-python语句中Python循环语句(附代码)

    python语句多如牛毛,对于很多初学者来说,不知道该如何下手.今天本文将着重讲述python语句中for语句和while语句.都知道这2种语句都属于循环语句,for语句属于遍历循环,while语句属 ...

  7. python 如何在一个for循环中遍历两个列表

    是我在看<笨方法学python>过程中发现有一行代码看不懂--" for sentence in snippet, phrase:",所以研究了半天,感觉挺有收获的.所 ...

  8. python中superclass是什么_深度解析并实现python中的super(转载,好文)

    大神半个月的成绩,让我看的叹为观止,建议看原帖地址,会让你对Python的描述符有更强的认识. 原文链接:https://blog.csdn.net/zhangjg_blog/article/deta ...

  9. python函数设置默认参数_深入讲解Python函数中参数的使用及默认参数的陷阱

    这篇文章主要介绍了Python函数中参数的使用及默认参数的陷阱,文中将函数的参数分为必选参数.默认参数.可变参数和关键字参数来讲,要的朋友可以参考下 C++里函数可以设置缺省参数,Java不可以,只能 ...

最新文章

  1. 大神教你如何给脚本写一个守护进程
  2. 【LINUX】Oracle数据库 linux磁盘头数据损坏修复
  3. iPhone的录音和播放
  4. 职业发展鉴定方法:第一行:自己行 第二行:大家说你行 第三行:领导行 就这么简单,去努力吧,如果你做到了这三个行,那么你肯定行...
  5. 如何修改和配置idea编辑器的编码
  6. 算法高级(24)-一致性哈希算法在分布式系统中的使用场景
  7. 实现JNI的另一种方法:使用RegisterNatives方法传递和使用Java自定义类 (转)
  8. 网络专业人士笔记(7~11章)
  9. linux拷贝文件后几百行,我 的 一 些 练 习 题
  10. Keil C51大工程建立,模块化编程
  11. Nodejs—即时通讯
  12. 内网渗透- *** PTH(传递哈希)***PTT(传递票据)***PTK(传递密钥)
  13. 如何对nginx进行平滑升级
  14. 这是我家养的睡莲 开花了
  15. Java-Tomcat的请求参数解析分析
  16. 科兴新冠疫苗获准紧急用于巴西3至5岁儿童
  17. LAL-开源Go语言音视频流媒体服务器
  18. Ensight 基本操作
  19. 完整版 html代码简洁说明,【完整版-HTML代码简介说明-适合新手】讲述.ppt
  20. 以大数据、云计算、人工智能、区块链为代表的新技术,开始充当底层技术

热门文章

  1. ecshop 删除订单时提示“以下订单无法被移除”的解决办法
  2. menuItem无法响应点击事件
  3. 深入理解JS中的变量作用域
  4. 机房收费系统中——存储过程中加入事务,实现学生注册
  5. java HelloWorld 编程风格实践
  6. C语言入门书籍--C语言程序设计
  7. 网站的高性能架构-性能测试方法
  8. 口语学习Day4:今天带你们一起逛一下博物馆
  9. STM32外部中断与各通道对应关系
  10. 汇编: 在代码中安排自己定义的数据,栈空间