数学建模社区-数学中国
标题:
Python 迭代器、生成器
[打印本页]
作者:
zhangtt123
时间:
2020-6-16 10:18
标题:
Python 迭代器、生成器
一、可迭代对象
: J% L R- K+ O* k6 W* z( y& N
字面意思:
( x7 f, ]+ v& a6 D& _" `7 V
对象:Python 中一切皆为对象(巧了 Java 也是(手动滑稽))
9 J8 t# _+ s( V3 \; K
可迭代:可更新迭代,重复、循环的一个过程,每次更新迭代都会获得新的内容
. H1 @, _4 M( g4 i+ E
专业角度:内部含有 '__iter__‘ 方法的对象
; b3 d5 ~# l7 @' }
目前学过的可迭代对象:str、list、tuple、dict、set、range、文件句柄等
2 T3 f6 J2 ], S8 O3 _, G% L
判断一个对象是否是可迭代对象:看是否有 '__iter__' 方法,dir() 可以获取一个对象的所有方法;或者使用 isinstance(object, collections.iterable) 来判断对象是否是可迭代对象的一个实例
# l" q3 ~& s& s) |: ~
' q+ ?. j$ }$ g, r4 @4 S+ E2 s+ M
1 E9 m9 S, h' t p# B6 Q; M2 \! O; B' \6 S
优点:
+ r; I, l4 {2 S- }& V/ Q# T# Y
存储的数据直接能显示,比较直观:比如直接 print 一个可迭代对象,就会调用 __str__ 方法(相当于 Java 中的 toString),把可迭代对象的值打印出来
; Z" F$ K+ G) p9 Y7 ~8 f. e6 U2 P
拥有较多的方法,操作方便:增删查改等
9 K0 U, G0 S; h1 y7 P8 E
缺点:
, W' W1 s6 U# g5 |7 [4 j
占用内存:一旦创建了一个可迭代对象,就会将该对象的内容全部加载到内存中
4 ~) b9 p# Z3 I3 @! X0 V
不能直接通过 for 循环,不能直接取值(通过索引、key等)。诸如通过 for i in iterable 这种形式获取元素实际上也是调用了 __iter__ 方法先将可迭代对象转换成迭代器再进行获取
4 E0 J/ P% c$ J b; a
二、迭代器
& A( j& E( s/ @2 m( f
字面意思:器,工具,迭代器也就是可以一直更新迭代取值的工具
, F) R# Z' k+ [% H
专业角度:内部含有 __iter__ 方法且含有 __next__ 方法的对象就是迭代器;或者使用 isinstance(object, collections.iterator) 来判断对象是否是可迭代对象的一个实例
& N1 U$ W4 H; f& H
把一个可迭代对象转换成迭代器:使用 iter() 方法或使用对象的 __iter__ 方法
- V; `7 Y( Q4 y' h/ P- e) n* `' m. v3 d
/ |5 M6 e+ t% f. n
迭代器取值:使用 next() 方法或对象的 __next__ 方法;当迭代器的值去玩了继续取,就会报StopIteration异常,所以一般使用迭代器需要做异常处理
8 T3 r. @1 L9 T
, n8 n [# B3 D A$ h& M
优点
. A& |2 a9 l/ g0 o/ x
节省内存:迭代器并不会一次性将对象的值全部加载到内存中,而是需要时才加载(类似 sed)
7 h, {) s# h5 X3 e3 ]& K( j
惰性机制:next 一次只取一个值,绝对不多取
R5 V2 G5 b. r# X3 q
缺点:
& C; w$ m9 \3 N' ?. \ n
速度慢:需要一直 next
& r* g) E/ }# }9 O2 M: W
不能回头:只能一直往下取值,取过的值没保存就没了`
, q# ~6 M8 ]9 I' b
不能直观的看到里面的数据
* L! }5 X" \1 q* a# d
三、可迭代对象与迭代器对比
" N) ^8 }/ u8 L0 v& h0 C
可迭代对象:
1 i6 k, v, q7 W5 m3 A; @ b
私有方法多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等)
8 [/ C' U) Z! d( R8 }4 x$ b
直观,可以直接看到里面的数据
( s x( T* t. V! U5 e# G
占用内存
: d. ^9 I7 \, ?$ a1 W
不能直接通过循环迭代取值
6 I- C, i6 v4 z7 s
应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择
. A0 A! t+ I9 [9 D; @
迭代器:
5 P7 V' T* G9 s7 s% @
节省内存,按需取值
% k* K9 X4 Y; C3 j) T
可以直接通过循环迭代取值
& e2 ^" o, e( E( ~& ?) ^
数据不直观,操作方法单一
: _4 ?$ q! R1 P. \5 \1 K! n) P7 c
应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择
! V3 j- l7 p8 A/ `. r- m
四、生成器
3 c: i4 |) Q+ s( ]
生成器的本质就是迭代器,唯一的区别是生成器是我们自己用代码构建的数据结构,迭代器是 Python 提供的,或者通过可迭代对象转化得来的
P) E4 a1 r" [# Z" u T
- C' i Z. y3 Z$ c6 f
定义生成器的方式:
9 c9 e" X" I* T( Y& C- o
通过生成器函数构建生成器
. A0 Q& o" x' D2 o8 P
2020-6-16 10:15 上传
下载附件
(3.62 KB)
1 M0 p+ D v( M) ?- r
$ s' @ K6 O1 K
4 q8 q3 D# J! d4 Q
这就是最简单的生成器函数。实际上这个 yield 就替代了 return,不仅将函数变成了生成器函数,还会将后面的值在调用 __next__ 的时候返回出来
, y; r2 L c; J8 X- [5 U
( r- x6 W1 H( ?, _8 G
也可以在一个函数里定义多个 yield
h, X3 g/ F/ M. u; R1 t7 W8 C; z
7 B/ D5 @7 ^- q8 K, t) D
之前说过,生成器本质上还是迭代器,一个 yield 对应一个 next,当 next 的数量超过了 yield,就会报 StopIteration
3 Y" q4 @6 @8 D. `
( V& W% Y9 o2 S* E8 I
yield 与 return 的区别
1 R z/ W4 d, B; a+ y: e, d* o$ j& c
" Q: Z% |% ^: ?9 ]( F& m6 e
return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值
1 @9 I* _8 C. C2 ]8 D8 @3 n1 u* \& K
yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素
4 y5 t1 c% M9 g0 y& D% s$ o
应用举例:
! x; d: C6 Q2 w0 w1 m+ E
7 [8 l8 }$ m) X- Y
买 5000 个包子,假设这个老板很厉害,一下子就把 5000 个包子做出来卖给我们,可是我们只有 5 个人,一下子吃不完,那包子就会冷掉、臭掉、被丢掉浪费了
2 X5 E7 q7 u- I% K* c
2020-6-16 10:16 上传
下载附件
(11.27 KB)
) ?2 e6 ~% S: j& O% l" g
7 N3 M6 g' n' d8 t% L1 m# x
如果这个老板可以在我们需要多少个包子就做出来多少个包子的话,这样做出来的包子就不会被浪费了(比如我们每个人一口气能吃 40 个包子,那每次就做 200 个包子):
& _/ K& a: ]" L6 Z; _. h& d1 i0 X }
2020-6-16 10:17 上传
下载附件
(12.4 KB)
1 F* l" Z+ Q/ q: a J* }
* R9 d9 X7 W! L( v
除了 使用 next() 触发 yield 之外,生成器还有一种方法 send(),这个方法可以在调用 yield 的同时传值给生成器内部
0 s! l' T( ?1 c* q1 K# f
: K% h6 `: {2 N2 e
可以看到在使用 next() 的时候,只能获取到 yield 的值,但不能传递值
" c- z" l" N& F ^
) N' s5 s1 u" Y' x
在使用 send() 的时候,可以将参数传入生成器中使用
6 I/ W- e/ j2 Y
' v) r0 c' W2 `/ q
需要注意的是第一次不能直接调用 send() 传参,因为每次调用生成器的时候,实际上只会返回 yield 后面的内容,然后生成器就停止了(睡眠了?),而 send() 传入的参数要通过 yield 传入生成器中(每次调用生成器在 yield 停止,然后在 yield 恢复继续允许),第一次调用并没有 yield 给我们传入参数,可以使用 send(None),可以打断点自己分析一下
# m3 e% j, D: P3 P. ~+ { T
; A5 p( f& r1 B8 Y3 M9 G
yield 会将它后面跟着的对象直接返回,如果它后面跟着的是可迭代对象,也可以使用 yield from 将这个可迭代对象变成迭代器返回
- }1 z3 s) M6 u# H& w* D& Y& o
* V: z- E' G7 }& w" r: N* y
* w/ S) _9 R" Z/ Y
yield from 是将列表中的每一个元素返回,所以写两个 yield from 并不会有交替执行的效果
) }- a9 P5 N' r( I. ~
; S2 b' J/ o/ Y1 w
0 e- s, q! r# U5 T# }" j1 R0 t
* c% R4 w- s) n2 _
通过推导式构建生成器
2 W# [1 F' c6 F" L/ g" N
列表推导式:
9 [9 U# O+ T( N6 d1 t
& @- `/ q m: c7 f/ m1 b& i
生成器表达式:和列表推导式差不多,把 [] 改成 () 即可
. n" }# O/ ]: o, K! l% b* E
# H. i# K* u! h9 g: e' S* L* ?
3 C1 e, `1 _/ m. n8 q% f# i% X
列表推导式和生成器推导式的区别:
1 V2 r0 b4 G! w' P5 Y
8 B5 Q6 Y6 R( h h! G' Q1 F- G- a
列表推导式比较耗内存,所有数据一次性加载到内存;而生成器表达式遵循迭代器协议,逐个产生元素
8 C8 K1 C4 s [+ ~( F" y. t
得到的值不一样:列表推导式得到的是一个列表;生成器表达式获取的是一个生成器
# u4 x* Z6 U. M# p- L
列表推导式一目了然,生成器表达式只是一个内存地址
1 t7 M! o. a) [0 ]- h
————————————————
( F/ d0 {) K' O; _7 ~
版权声明:本文为CSDN博主「阿玮d博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
7 }& U/ _( b( _) `+ T
原文链接:https://blog.csdn.net/weixin_42511320/article/details/105676143
) k4 a6 j" E2 }1 E& i9 G
9 W' y$ _1 o$ U: q
* q$ S- `- r1 r! B& f7 p
作者:
dwadasd
时间:
2020-6-24 20:40
多谢大神分享
& X- `6 N7 a+ b7 N O1 t
作者:
dwadasd
时间:
2020-6-24 20:41
太好了太好了
7 r. a0 H# f/ w& h6 e9 V: m
欢迎光临 数学建模社区-数学中国 (http://www.madio.net/)
Powered by Discuz! X2.5