[TOC]
## 列表生成式
通过列表生成式,我们可以直接创建一个列表
```
>>> a = [i+1 for i in range(10)]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```
>通过列表生成式,我们可以直接创建一个列表。但是,列表容量肯定是有限的。如果创建一个包含100万个元素的列表,不仅占用很大的存储空间,假如我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
## 生成器(generator)
通过生成器一边循环一边计算的机制,就不必创建完整的list,从而节省大量的空间,如果独立函数调用,函数可以暂停或者挂起,并在需要时从暂停地方继续或者重新开始,需要用`yield`实现
### 列表生成器
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的`[]`改成`()`,就创建了一个generator:
```
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
```
创建`L`和`g`的区别仅在于最外层的`[]`和`()`,`L`是一个list,而`g`是一个generator。
#### 获取生成器的值(next函数)
生成器中的数据,可以通过`next()`函数获得generator的下一个返回值:
```
>>> next(g)
0
......略
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
```
>generator保存的是算法,每次调用`next(g)`就计算出`g`的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出`StopIteration`的错误。
#### 获取生成器的值(for循环)
不断调用`next(g)`实在是太变态了,正确的方法是使用`for`循环,因为generator也是可迭代对象:
```
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
......略
81
```
>创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。
### 函数生成器
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
如果一个函数定义中包含`yield`关键字,那么这个函数就不再是一个普通函数,而是一个generator:
#### 斐波拉契数列生成器
著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
`1, 1, 2, 3, 5, 8, 13, 21, 34, ...`
```
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
```
要把`fib`函数变成generator,只需要把`print(b)`改为`yield b`就可以了:
```
def fib(max):
n,a,b = 0,0,1
while n < max:
#print(b)
yield b
a,b = b,a+b
n += 1
return 'done'
```
验证一下这个是不是编程函数生成器
```
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>
```
#### 函数生成器与函数的区别
最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时从上次返回的yield语句处继续执行。
```
data = fib(10)
print(data)
print(data.__next__())
print(data.__next__())
print("干点别的事")
print(data.__next__())
print(data.__next__())
#>:输出
<generator object fib at 0x000002E33EEFFCA8>
1
1
干点别的事
2
3
```
> `在上面fib`的例子,我们在循环过程中不断调用`yield`,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们基本上从来不会用`next()`来获取下一个返回值,而是直接使用`for`循环来迭代:
```
>>> for n in fib(6):
... print(n)
...
1
1
......略
8
```
### 捕获生成器错误(StopIteration)
用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
```
>>> g = fib(6)
>>> while True:
... try:
... x = next(g)
... print('g:', x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
g: 1
g: 1
......略
g: 8
Generator return value: done
```
> 关于如何捕获错误的详细情况,看错误捕获相关笔记。
## 迭代器(Iterator)
迭代器就是可将可迭代对象(如列表、元组、字典等)循环输出的一种工具,比如`for...in...`就是应用了迭代器的原理。
如果想转成迭代器可以用`iter()`函数,然后用`next()`函数就可以一个个内容输出了,或者也可以结合for输出,每输出一个,迭代器里就少一个,直到空了就不能输出了
我们已经知道,可以直接作用于`for`循环的数据类型有以下两类种:
* 一类是集合数据类型,如`list`、`tuple`、`dict`、`set`、`str`等;
* 一类是`generator`,包括生成器和带`yield`的generator function。
### 可迭代对象(Iterable)
这些可以直接作用于`for`循环的对象统称为可迭代对象:`Iterable`。可以使用`isinstance()`判断一个对象是否是`Iterable`对象:
```
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False`
```
### 迭代器(Iterator)
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
**可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。**
可以使用isinstance()判断一个对象是否是Iterator对象:
```
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
```
生成器都是`Iterator`对象,但`list`、`dict`、`str`虽然是`Iterable`,却不是`Iterator`。
### 可迭代对象转为迭代器
把`list`、`dict`、`str`等`Iterable`变成`Iterator`可以使用`iter()`函数:
```
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
```
>为什么`list`、`dict`、`str`等数据类型不是`Iterator`?
>>这是因为Python的`Iterator`对象表示的是一个数据流,Iterator对象可以被`next()`函数调用并不断返回下一个数据,直到没有数据时抛出`StopIteration`错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过`next()`函数实现按需计算下一个数据,所以`Iterator`的计算是惰性的,只有在需要返回下一个数据时它才会计算。
`Iterator`甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
## 迭代器概念总结
* 凡是可作用于`for`循环的对象都是`Iterable`类型;
* 凡是可作用于`next()`函数的对象都是`Iterator`类型,它们表示一个惰性计算的序列;
* 集合数据类型如`list`、`dict`、`str`等是`Iterable`但不是`Iterator`,不过可以通过`iter()`函数获得一个`Iterator`对象。
- 基础部分
- 基础知识
- 变量
- 数据类型
- 数字与布尔详解
- 列表详解list
- 字符串详解str
- 元组详解tup
- 字典详解dict
- 集合详解set
- 运算符
- 流程控制与循环
- 字符编码
- 编的小程序
- 三级菜单
- 斐波那契数列
- 汉诺塔
- 文件操作
- 函数相关
- 函数基础知识
- 函数进阶知识
- lambda与map-filter-reduce
- 装饰器知识
- 生成器和迭代器
- 琢磨的小技巧
- 通过operator函数将字符串转换回运算符
- 目录规范
- 异常处理
- 常用模块
- 模块和包相关概念
- 绝对导入&相对导入
- pip使用第三方源
- time&datetime模块
- random随机数模块
- os 系统交互模块
- sys系统模块
- shutil复制&打包模块
- json&pickle&shelve模块
- xml序列化模块
- configparser配置模块
- hashlib哈希模块
- subprocess命令模块
- 日志logging模块基础
- 日志logging模块进阶
- 日志重复输出问题
- re正则表达式模块
- struct字节处理模块
- abc抽象类与多态模块
- requests与urllib网络访问模块
- 参数控制模块1-optparse-过时
- 参数控制模块2-argparse
- pymysql数据库模块
- requests网络请求模块
- 面向对象
- 面向对象相关概念
- 类与对象基础操作
- 继承-派生和组合
- 抽象类与接口
- 多态与鸭子类型
- 封装-隐藏与扩展性
- 绑定方法与非绑定方法
- 反射-字符串映射属性
- 类相关内置方法
- 元类自定义及单例模式
- 面向对象的软件开发
- 网络-并发编程
- 网络编程SOCKET
- socket简介和入门
- socket代码实例
- 粘包及粘包解决办法
- 基于UDP协议的socket
- 文件传输程序实战
- socketserver并发模块
- 多进程multiprocessing模块
- 进程理论知识
- 多进程与守护进程
- 锁-信号量-事件
- 队列与生产消费模型
- 进程池Pool
- 多线程threading模块
- 进程理论和GIL锁
- 死锁与递归锁
- 多线程与守护线程
- 定时器-条件-队列
- 线程池与进程池(新方法)
- 协程与IO模型
- 协程理论知识
- gevent与greenlet模块
- 5种网络IO模型
- 非阻塞与多路复用IO实现
- 带着目标学python
- Pycharm基本使用
- 爬虫
- 案例-爬mzitu美女
- 案例-爬小说
- beautifulsoup解析模块
- etree中的xpath解析模块
- 反爬对抗-普通验证码
- 反爬对抗-session登录
- 反爬对抗-代理池
- 爬虫技巧-线程池
- 爬虫对抗-图片懒加载
- selenium浏览器模拟