for...in 是Python程序员使用最多的语句,for 循环用于迭代容器对象中的元素,这些对象可以是列表、元组、字典、集合、文件,甚至可以是自定义类或者函数,例如:

作用于列表

>>> for elem in [1,2,3]:
...    print(elem)
...
1
2
3

作用于元组

>>> for i in ("zhang", "san", 30):
...    print(i)
...
zhang
san
30

作用于字符串

>>> for c in "abc":
...     print(c)
...
a
b
c

作用于集合

>>> for i in {"a","b","c"}:
...    print(i)
...
b
a
c

作用于字典

>>> for k in {"age":10, "name":"wang"}:
...    print(k)
...
age
name

作用于文件

>>> for line in open("requirement.txt"):
...    print(line, end="")
...
Fabric==1.12.0
Markdown==2.6.7

可能有人不经要问,为什么这么多不同类型对象都支持 for 语句,还有哪些类型的对象可以作用在 for 语句中呢?回答这个问题之前,我们先要了解 for 循环背后的执行原理。

for 循环是对容器进行迭代的过程,什么是迭代?迭代就是从某个容器对象中逐个地读取元素,直到容器中没有更多元素为止。那么,哪些对象支持迭代操作?任何对象都可以吗?先随便自定义一个类试试,看行不行:

>>> class MyRange:
...    def __init__(self, num):
...        self.num = num
...
>>> for i in MyRange(10):
...    print(i)
...
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: 'MyRange' object is not iterable

错误堆栈日志非常清楚地告诉我们,MyRange 不是一个可迭代对象,所以它不能用于迭代,到底什么样的对象才称得上是可迭代对象(iterable)呢?

可迭代对象需要实现__iter__方法,并返回一个迭代器,迭代器(Iterator)只需要实现 __next__方法。现在我们就来验证一下列表为什么支持迭代:

>>> x = [1,2,3]
>>> its = x.__iter__() # x有此方法,说明列表是可迭代对象
>>> its
<list_iterator object at 0x100f32198> >>> its.__next__()  # its有此方法,说明its是迭代器
1
>>> its.__next__()
2
>>> its.__next__()
3
>>> its.__next__()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration

从试验结果来看,列表是一个可迭代对象,因为它实现了 __iter__方法,并且返回了一个迭代器对象(list_iterator)因为它实现了 __next__方法我们看到它不断地调用__next__方法,其实就是不断地迭代获取容器中的元素,直到容器中没有更多元素抛出 StopIteration 异常为止。

那么 for 语句又是如何循环的呢?到这里,恐怕你也猜到了,它的步骤是:

  1. 先判断对象是否为可迭代对象,不是的话直接报错,抛出TypeError异常,是的话,调用 __iter__方法,返回一个迭代器

  2. 不断地调用迭代器的__next__方法,每次按序返回迭代器中的一个值

  3. 迭代到最后,没有更多元素了,就抛出异常 StopIteration,这个异常 python 自己会处理,不会暴露给开发者

对于元组,字典,字符串也是同样的道理,弄明白了 for 的执行原理之后,我们就可以实现自己的迭代器用在 for 循环中。

前面的 MyRange 报错是因为它没有实现迭代器协议里面的这两个方法,现在继续改进:

class MyRange:
   def __init__(self, num):
       self.i = 0
       self.num = num    def __iter__(self):
       return self    def __next__(self):
       if self.i < self.num:
           i = self.i
           self.i += 1
           return i
       else:
           # 达到某个条件时必须抛出此异常,否则会无止境地迭代下去
           raise StopIteration()

因为它实现了__next__方法,所以 MyRange 本身已经是一个迭代器了,所以 __iter__返回的就是对象本身 self。现在用在 for 循环中试试:

for i in MyRange(3):
   print(i)
# 输出
1
2
3

你会发现 MyRange 功能和内建函数 range很相似。for 循环本质是不断地调用迭代器的__next__方法,直到出现 StopIteration 异常退出循环。所以任何可迭代对象都可以作用在for循环中。

for循环是怎么工作的的更多相关文章

  1. Shell脚本- 单条命令循环执行重复工作

    关于shell for循环具体详细说明可参考:http://wiki.jikexueyuan.com/project/linux-command/chap34.html example: 分别在com ...

  2. JavaScript是如何工作的:事件循环和异步编程的崛起 + 5种使用 async/await 更好地编码方式!

    摘要: 深度理解JS事件循环!!! 原文:JavaScript是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式! 作者:前端小智 Fundebug经授权转载, ...

  3. JavaScript是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!

    为什么单线程是一个限制? 在发布的第一篇文章中,思考了这样一个问题:当调用堆栈中有函数调用需要花费大量时间来处理时会发生什么? 例如,假设在浏览器中运行一个复杂的图像转换算法. 当调用堆栈有函数要执行 ...

  4. JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧

    原文地址:How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding wi ...

  5. ORACLE工作原理小结

    ORACLE工作原理1-连接 我们从一个用户请求开始讲,ORACLE的完整的工作机制是怎样的,首先一个用户进程发出一个连接请求,如果使用的是主机命名或者是本地服务命中的主机名使用的是机器名(非IP地址 ...

  6. [转]Java 8:不要再用循环了

    以下内容为转载,没有在jdk8中测试,具体业务场景是否存在BUG或使用需要注意的地方有待测试. ------------------分割线---------------------- 正如我之前所写的 ...

  7. 例行性工作排程 (crontab)

    1. 什么是例行性工作排程 1.1 Linux 工作排程的种类: at, crontab 1.2 Linux 上常见的例行性工作2. 仅运行一次的工作排程 2.1 atd 的启动与 at 运行的方式: ...

  8. Java 8:不要再用循环了

    本文由 ImportNew - 进林 翻译自 deadcoderising.欢迎加入翻译小组.转载请见文末要求. 正如我之前所写的,Java 8中的新功能特性改变了游戏规则.对Java开发者来说这是一 ...

  9. 你好,C++(21)只要天还没黑,就一直在工地干活-4.3.1 while循环:只要…就一直…

    4.3  循环控制语句 在现实世界中,有这样一类现象: 只要油箱中的当前油量小于油箱容量100升,就一直往油箱中加油: 一直不断地为祖国辛勤工作,只要我还活着: 公司100000位员工,每个人的工资都 ...

随机推荐

  1. 辨析element.offsetXxxx和element.style.xxxx

    DOM操作时,经常使用element.style属性,没错,element.style是属性,和几个offsetXxxx属性一样,概念是一样的. 但是style有几个属性,这几个属性和offsetXx ...

  2. marMariaDB & MYSQL flexviews

    Using Flexviews - part one, introduction to materialized views - Percona Database Performance Bloght ...

  3. js压箱底的宝贝

    框架的确好用, 不过他们也隐藏了JavaScript中丑陋的细节和DOM的运作机制. 如果你的目标是敢于自称"我懂JavaScript", 那么花时间学习框架无异于南辕北辙. 下面 ...

  4. jQuery EasyUI布局容器layout实例精讲

    这个布局容器,有五个区域:北.南.东.西和中心. 他中心地区面板是必需的,但是边缘地区面板是可选的.每一个边缘地区面板可以缩放的拖动其边境, 他们也可以通过点击其收缩触发.布局可以嵌套,从而用户可以建 ...

  5. spring AOP源码分析(三)

    在上一篇文章 spring AOP源码分析(二)中,我们已经知道如何生成一个代理对象了,那么当代理对象调用代理方法时,增强行为也就是拦截器是如何发挥作用的呢?接下来我们将介绍JDK动态代理和cglib ...

  6. JS对象、基本类型和字面量的区别

    字面值: var str1='this is a simple string'; var num1=1.45; var answer1=true; 基本类型: var str2=String('thi ...

  7. java.lang.NoClassDefFoundError: org/apache/log4j/Priority的问题解决

    在pom 文件中添加 <dependency> <groupId>log4j</groupId> <artifactId>log4j</artif ...

  8. python之路--操作系统介绍,进程的创建

    一 .  操作系统的作用: 1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口 2:管理.调度进程,并且将多个进程对硬件的竞争变得有序 二 多道技术: 所谓多道程序设计技术,就是指允许多个程序同时进入内存 ...

  9. python数据结构与算法第十七天【概率算法】

    1. 古典概率 例如:麻将开始摸到的14张牌中无将的概率,两张相同的牌即为将,则有: 所有的情况:从136张牌中选出14张牌,为C136-14 无将的情况:将不同的牌分组,共有34组,依次取14张牌, ...

  10. 日历插件bootstrap-datetimepicker的使用感悟

    首先队长先综述一下插件的使用三步流程:即 1.引入插件  2.使用jquery选择器选择目标标签  3.对目标标签绑定插件函数来触发插件 雷同于python中的库的使用(安装库 导入库 引用库) 下面 ...