python 进阶篇 迭代器和生成器深入理解
列表/元组/字典/集合都是容器。对于容器,可以很直观地想象成多个元素在一起的单元;而不同容器的区别,正是在于内部数据结构的实现方法。
所有的容器都是可迭代的(iterable)。另外字符串也可以被迭代。
迭代器类比
迭代可以想象成是你去买苹果,卖家并不告诉你他有多少库存。这样,每次你都需要告诉卖家,你要一个苹果,然后卖家采取行为:要么给你拿一个苹果;要么告诉你,苹果已经卖完了。你并不需要知道,卖家在仓库是怎么摆放苹果的。
严谨地说,迭代器(iterator)
提供了一个 next
(可以不重复不遗漏地一个一个拿到所有元素) 的方法。调用这个方法后,你要么得到这个容器的下一个对象,要么得到一个 StopIteration
的错误(苹果卖完了)。
示例,判断是否可迭代
from collections.abc import Iterable
params = [
1234,
'1234',
[1, 2, 3, 4],
set([1, 2, 3, 4]),
{1:1, 2:2, 3:3, 4:4},
(1, 2, 3, 4)
]
for param in params:
print('{} is iterable? {}'.format(param, isinstance(param, Iterable)))
# 输出
# 1234 is iterable? False
# 1234 is iterable? True
# [1, 2, 3, 4] is iterable? True
# {1, 2, 3, 4} is iterable? True
# {1: 1, 2: 2, 3: 3, 4: 4} is iterable? True
# (1, 2, 3, 4) is iterable? True
生成器类比
生成器可以想象成是你去买苹果,卖家并没有库存。这样,每次你都需要告诉卖家,你要一个苹果,然后卖家采取行为,立马生成 1 个苹果(生成速度极快):要么给你拿一个苹果;要么告诉你,苹果已经卖完了。
生成器是懒人版本的迭代器
示例,迭代器与生成器的对比
import os
import psutil
import time
import functools
def log_execution_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
res = func(*args, **kwargs)
end = time.perf_counter()
print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
return res
return wrapper
# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024. / 1024
print('{} memory used: {} MB'.format(hint, memory))
@log_execution_time
def test_iterator():
show_memory_info('initing iterator')
list_1 = [i for i in range(100000000)]
show_memory_info('after iterator initiated')
print(sum(list_1))
show_memory_info('after sum called')
@log_execution_time
def test_generator():
show_memory_info('initing generator')
list_2 = (i for i in range(100000000))
show_memory_info('after generator initiated')
print(sum(list_2))
show_memory_info('after sum called')
test_iterator()
print()
test_generator()
########## 输出 ##########
# initing iterator memory used: 10.16796875 MB
# after iterator initiated memory used: 3664.34765625 MB
# 4999999950000000
# after sum called memory used: 3664.34765625 MB
# test_iterator took 6179.794754018076 ms
# initing generator memory used: 19.140625 MB
# after generator initiated memory used: 19.14453125 MB
# 4999999950000000
# after sum called memory used: 19.171875 MB
# test_generator took 4912.561981996987 ms
迭代器是一个有限集合
,生成器则可以成为一个无限集
。
我们并不需要在内存中同时保存这么多东西,比如对元素求和,我们只需要知道每个元素在相加的那一刻是多少就行了,用完就可以扔掉了。
于是,生成器的概念应运而生,在你调用 next()
函数的时候,才会生成下一个变量。生成器在 Python 的写法是用小括号括起来,(i for i in range(100000000))
,即初始化了一个生成器。
这样一来,你可以清晰地看到,生成器并不会像迭代器一样占用大量内存,只有在被使用的时候才会调用。而且生成器在初始化的时候,并不需要运行一次生成操作,相比于 test_iterator()
,test_generator()
函数节省了一次生成一亿个元素的过程,因此耗时明显比迭代器短。
示例,数学中有一个恒等式,(1 + 2 + 3 + ... + n)^2 = 1^3 + 2^3 + 3^3 + ... + n^3 的证明
def generator(k):
i = 1
while True:
yield i ** k
i += 1
gen_1 = generator(1)
gen_3 = generator(3)
print(gen_1)
print(gen_3)
def get_sum(n):
sum_1, sum_3 = 0, 0
for i in range(n):
next_1 = next(gen_1)
next_3 = next(gen_3)
print('next_1 = {}, next_3 = {}'.format(next_1, next_3))
sum_1 += next_1
sum_3 += next_3
print(sum_1 * sum_1, sum_3)
get_sum(8)
########## 输出 ##########
# <generator object generator at 0x10c30d3d0>
# <generator object generator at 0x10c6d61d0>
# next_1 = 1, next_3 = 1
# next_1 = 2, next_3 = 8
# next_1 = 3, next_3 = 27
# next_1 = 4, next_3 = 64
# next_1 = 5, next_3 = 125
# next_1 = 6, next_3 = 216
# next_1 = 7, next_3 = 343
# next_1 = 8, next_3 = 512
# 1296 1296
接下来的 yield 是魔术的关键。对于初学者来说,你可以理解为,函数运行到这一行的时候,程序会从这里暂停,然后跳出,不过跳到哪里呢?答案是 next()
函数。那么 i ** k
是干什么的呢?它其实成了 next()
函数的返回值。这样,每次 next(gen) 函数被调用的时候,暂停的程序就又复活了,从 yield 这里向下继续执行;同时注意,局部变量 i 并没有被清除掉,而是会继续累加。我们可以看到 next_1 从 1 变到 8,next_3 从 1 变到 512。
示例,给定两个序列,判定第一个是不是第二个的子序列。
LeetCode 链接如下:https://leetcode.com/problems/is-subsequence/
先来解读一下这个问题本身。序列就是列表,子序列则指的是,一个列表的元素在第二个列表中都按顺序出现,但是并不必挨在一起。举个例子,[1, 3, 5] 是 [1, 2, 3, 4, 5] 的子序列,[1, 4, 3] 则不是。
def is_subsequence(ls, sub):
ls = iter(ls)
return all(i in ls for i in sub)
print(is_subsequence([1, 2, 3, 4, 5],[1, 3, 5]))
print(is_subsequence([1, 2, 3, 4, 5],[1, 4, 3]))
########## 输出 ##########
# True
# False
python 进阶篇 迭代器和生成器深入理解的更多相关文章
- Python进阶之迭代器和生成器
可迭代对象 Python中任意的对象,只要它定义了可以返回一个迭代器的__iter__方法,或者定义了可以支持下标索引的__getitem__方法,那么它就是一个可迭代对象.简单来说,可迭代对象就是能 ...
- python进阶篇
python进阶篇 import 导入模块 sys.path:获取指定模块搜索路径的字符串集合,可以将写好的模块放在得到的某个路径下,就可以在程序中import时正确找到. import sys ...
- python设计模式之迭代器与生成器详解(五)
前言 迭代器是设计模式中的一种行为模式,它提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示.python提倡使用生成器,生成器也是迭代器的一种. 系列文章 python设计模 ...
- Python入门篇-解析式、生成器
Python入门篇-解析式.生成器 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.列表解析式(List Comprehension) 1>.列表解析式语法 语法 [ 返回 ...
- python is、==区别;with;gil;python中tuple和list的区别;Python 中的迭代器、生成器、装饰器
1. is 比较的是两个实例对象是不是完全相同,它们是不是同一个对象,占用的内存地址是否相同 == 比较的是两个对象的内容是否相等 2. with语句时用于对try except finally 的优 ...
- Python 闭包、迭代器、生成器、装饰器
Python 闭包.迭代器.生成器.装饰器 一.闭包 闭包:闭包就是内层函数对外层函数局部变量的引用. def func(): a = "哈哈" def func2(): prin ...
- 终于理解Python中的迭代器和生成器了!
迭代器和生成器 目录 迭代器和生成器 可迭代对象和迭代器 基础概念 判断 for循环本质 不想用for循环迭代了,如何使用迭代器? 列表推导式 生成器Generator 概念 如何实现和使用? 生成器 ...
- Python进阶:迭代器与迭代器切片
2018-12-31 更新声明:切片系列文章本是分三篇写成,现已合并成一篇.合并后,修正了一些严重的错误(如自定义序列切片的部分),还对行文结构与章节衔接做了大量改动.原系列的单篇就不删除了,毕竟也是 ...
- Python基础之迭代器和生成器
阅读目录 楔子 python中的for循环 可迭代协议 迭代器协议 为什么要有for循环 初识生成器 生成器函数 列表推导式和生成器表达式 本章小结 生成器相关的面试题 返回顶部 楔子 假如我现在有一 ...
随机推荐
- SVN: Cleanup failed update报错 文件被锁定lock办法,cleanup 失效报错。
按照如下办法即可解决: 下载文件链接: https://pan.baidu.com/s/1Ump1BFihbX8izyAA4by5dA 提取码: ftsd 复制这段内容后打开百度网盘手机App,操作更 ...
- sql server 数据库安装手册
1. 双击setup.exe运行安装程序,进入[SQL Server 安装中心] 2. 选择左侧菜单栏[安装],运行第一项[全新安装或向现有安装添加功能] 3. 进入[SQL Server 2008 ...
- 使用node.js中遇到的一些小bug
1.BUG Cannot set headers after they are sent to the client 解决:即发出一次请求得到两次或以上的回应时会出现此警告,此时注意查看再在一些条件下 ...
- Java多线程问题40个
1.多线程有什么用? 一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡.所谓”知其然知其所以然”,”会用”只是”知其然”,”为什么用”才是”知其所以然 ...
- Android学习----自定义Adapter实现ListView
前言: 对于ListView而言,自定义的Adapter对于显示复杂的界面有很大的灵活性 .使用自定义的Adapter需要继承BaseAdapter,然后重写getCount(),getView(), ...
- SQL Server 创建链接服务器的脚本,自定义链路服务器的简短名称
USE [master]GO /****** Object: LinkedServer [SQL01] Script Date: 2020/4/9 11:51:17 ******/EXEC m ...
- MySQL 同步复制及高可用方案总结
1.前言 mysql作为应用程序的数据存储服务,要实现mysql数据库的高可用.必然要使用的技术就是数据库的复制,如果主节点出现故障可以手动的切换应用到从节点,这点相信运维同学都是知道,并且可以实现的 ...
- 【tensorflow2.0】AutoGraph的机制原理
有三种计算图的构建方式:静态计算图,动态计算图,以及Autograph. TensorFlow 2.0主要使用的是动态计算图和Autograph. 动态计算图易于调试,编码效率较高,但执行效率偏低. ...
- 1013 Battle Over Cities (25 分)
It is vitally important to have all the cities connected by highways in a war. If a city is occupied ...
- Redis 设计与实现笔记 - SDS
Redis 中的字符串没有使用 C语言中的字符指针(char *),而是使用了自定义的结构 sds. 文件: sds.h sds.c 结构: struct sdshdr { int len; // 填 ...