数学建模社区-数学中国

标题: Python 迭代器、生成器 [打印本页]

作者: zhangtt123    时间: 2020-6-16 10:18
标题: Python 迭代器、生成器
一、可迭代对象
# I8 m) w' R% e, f+ g0 x$ p字面意思:
: Z! h- h# {+ C" O" p, a5 p对象:Python 中一切皆为对象(巧了 Java 也是(手动滑稽))
1 h4 G" n# o. C% J8 \. o1 _可迭代:可更新迭代,重复、循环的一个过程,每次更新迭代都会获得新的内容% ]$ l2 k; v8 ~  u5 b$ h& N3 z
专业角度:内部含有 '__iter__‘ 方法的对象
3 C  C8 `. x" l0 J/ n目前学过的可迭代对象:str、list、tuple、dict、set、range、文件句柄等, L- ?) t0 k" H, P
判断一个对象是否是可迭代对象:看是否有 '__iter__' 方法,dir() 可以获取一个对象的所有方法;或者使用 isinstance(object, collections.iterable) 来判断对象是否是可迭代对象的一个实例
6 \; V3 O: B2 ?* e+ j+ D
' O0 \& o; X! {- Q4 j; }
3 Z! S% M9 p/ I* j  ]  `, ~; v优点:
' Q, f2 i' v1 y1 G存储的数据直接能显示,比较直观:比如直接 print 一个可迭代对象,就会调用 __str__ 方法(相当于 Java 中的 toString),把可迭代对象的值打印出来
% |2 X: @0 H0 q* o拥有较多的方法,操作方便:增删查改等
$ ?& P/ n, O$ N缺点:6 ^/ [+ x* V( t/ J3 |& w" R& r5 y
占用内存:一旦创建了一个可迭代对象,就会将该对象的内容全部加载到内存中
! f  F) j2 n: e% r; X  b不能直接通过 for 循环,不能直接取值(通过索引、key等)。诸如通过 for i in iterable 这种形式获取元素实际上也是调用了 __iter__ 方法先将可迭代对象转换成迭代器再进行获取
5 [8 P, A0 n; [: U; x& V二、迭代器$ r; D( v2 _- U% k% }0 B. `5 X
字面意思:器,工具,迭代器也就是可以一直更新迭代取值的工具8 g. X# v+ B% ?$ _1 m) J" D
专业角度:内部含有 __iter__ 方法且含有 __next__ 方法的对象就是迭代器;或者使用 isinstance(object, collections.iterator) 来判断对象是否是可迭代对象的一个实例) n1 b( @$ r1 J* Q* g7 ~
把一个可迭代对象转换成迭代器:使用 iter() 方法或使用对象的 __iter__ 方法# {% U/ T4 z" a: e1 c
: A3 G# V8 Q) E) c1 t: Y  r: g4 p
迭代器取值:使用 next() 方法或对象的 __next__ 方法;当迭代器的值去玩了继续取,就会报StopIteration异常,所以一般使用迭代器需要做异常处理
6 F# i2 e3 n0 A- q- u: U! F4 T7 Z7 {2 L0 i4 V: R
优点
" Y' F2 |$ t2 b1 r- \) u5 I节省内存:迭代器并不会一次性将对象的值全部加载到内存中,而是需要时才加载(类似 sed)
. v7 X8 p2 j7 Z) s8 R+ Y. X惰性机制:next 一次只取一个值,绝对不多取
; b! ?! y; x& ^) _2 z* U缺点:* `  v2 e: U. O2 q5 i
速度慢:需要一直 next. c! E( X. o; u
不能回头:只能一直往下取值,取过的值没保存就没了`
1 G" T5 F  M) U4 w: F不能直观的看到里面的数据/ @! l' E/ E3 o$ b& i1 z# b
三、可迭代对象与迭代器对比
- E  l8 }7 f* e3 x* Y9 m% j可迭代对象:
2 r- g- S% y$ e# q% S8 w+ X私有方法多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等), [  q& @0 p; u: j1 ~
直观,可以直接看到里面的数据+ J# y) w2 {' F. U
占用内存
5 f. k8 v  i% z9 f7 Y4 O) {不能直接通过循环迭代取值
" F/ W, e3 I# {5 ^- \应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择
* q* q: Z- g3 B! ?) D1 W& v: [' u* Q6 V迭代器:; N$ ^. Z- k9 N7 ~+ H
节省内存,按需取值, |, X8 I) x  U' h; W+ ]
可以直接通过循环迭代取值* w1 ~5 k5 X( t2 Q8 g+ w: v
数据不直观,操作方法单一
7 o+ s3 ?/ G4 g# }% u应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择
" u4 M! l6 Z2 s5 r四、生成器
+ u0 k0 q* N! A( F; L生成器的本质就是迭代器,唯一的区别是生成器是我们自己用代码构建的数据结构,迭代器是 Python 提供的,或者通过可迭代对象转化得来的( C8 q( j  J0 i, F( `6 V0 i

* B" g! A$ M: y, E- _定义生成器的方式:* O& ~( k( X8 A$ O0 t
通过生成器函数构建生成器- I; d; d* S5 v
111.png
% o' j6 ~+ p& v; _) Y. J! p+ _. N  f; P) \) O1 d6 I

( S; N; }! F/ b& p4 _这就是最简单的生成器函数。实际上这个 yield 就替代了 return,不仅将函数变成了生成器函数,还会将后面的值在调用 __next__ 的时候返回出来+ v: }# u" w. P8 a8 r; z
* M0 g5 U3 S, c7 `* B0 z# m
也可以在一个函数里定义多个 yield
6 l" C, d1 h% K
' t( j: B0 O1 P5 t7 R# X之前说过,生成器本质上还是迭代器,一个 yield 对应一个 next,当 next 的数量超过了 yield,就会报 StopIteration) P0 s8 u9 g% T, q7 }
. B: n* I4 L7 J* J& H( t( _8 }
yield 与 return 的区别
& r) C+ o: G9 _: L! B" T, h* g, D! v
) ]. ], x1 i+ B6 ^3 p! h, wreturn一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值$ N. k# J5 F  i$ u/ L" B+ w3 L
yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素
" @- r& U5 I, W* W5 b" g2 ~' r# W应用举例:+ `8 F6 a% R) S+ L" o- `

, o- Z3 X7 Y4 S买 5000 个包子,假设这个老板很厉害,一下子就把 5000 个包子做出来卖给我们,可是我们只有 5 个人,一下子吃不完,那包子就会冷掉、臭掉、被丢掉浪费了; G5 [4 u" a3 b% O5 c% e5 p# o
222.png
  R5 f$ W3 W: i4 z' K# @
, {; a$ s& E, g+ U如果这个老板可以在我们需要多少个包子就做出来多少个包子的话,这样做出来的包子就不会被浪费了(比如我们每个人一口气能吃 40 个包子,那每次就做 200 个包子):( A, K0 J$ j3 d
3333.png # b* q$ ^' z% j; y

+ |4 T0 Y: m% V0 V除了 使用 next() 触发 yield 之外,生成器还有一种方法 send(),这个方法可以在调用 yield 的同时传值给生成器内部
; I! H* A( B" s* \/ P4 j; ^$ o  w  \7 e0 e. h& p  `
可以看到在使用 next() 的时候,只能获取到 yield 的值,但不能传递值
' R* g) z9 d# W8 c( h9 _/ n3 x3 ^5 N+ P0 L0 D+ T- m8 Q6 i
在使用 send() 的时候,可以将参数传入生成器中使用+ P: K5 l# r7 ]1 m! X
: q/ Y9 a* j! X5 f/ {, ^1 z. V
需要注意的是第一次不能直接调用 send() 传参,因为每次调用生成器的时候,实际上只会返回 yield 后面的内容,然后生成器就停止了(睡眠了?),而 send() 传入的参数要通过 yield 传入生成器中(每次调用生成器在 yield 停止,然后在 yield 恢复继续允许),第一次调用并没有 yield 给我们传入参数,可以使用 send(None),可以打断点自己分析一下, N; X/ e& ]3 _- G" X

" p$ |6 _* G7 `. \7 R/ Xyield 会将它后面跟着的对象直接返回,如果它后面跟着的是可迭代对象,也可以使用 yield from 将这个可迭代对象变成迭代器返回
- g/ J3 J6 X7 {8 h0 }' V4 e9 a: i! g  w9 \# j
9 b7 @+ P, m" r
yield from 是将列表中的每一个元素返回,所以写两个 yield from 并不会有交替执行的效果
: c+ S/ ]5 e& @, c/ M9 {: X. ?* F  p% Q* c

7 |0 x0 c; f" x# ~( F( f  n! R/ q" d7 z0 N) H5 \
通过推导式构建生成器) B  H1 s  Q* N1 p
列表推导式:
8 b; F! `" \  Z4 G! R$ p4 X) |9 s* k; \- x8 f
生成器表达式:和列表推导式差不多,把 [] 改成 () 即可
/ Q4 @+ Y8 r1 \5 A* e) r4 w3 n0 J
5 H3 f* ?, q+ J& E( `6 o2 u, ]8 d( s$ I5 v
列表推导式和生成器推导式的区别:
' _% n: F: ]2 E: N% g
$ l- S0 L8 t5 z3 K1 A% o# |* W列表推导式比较耗内存,所有数据一次性加载到内存;而生成器表达式遵循迭代器协议,逐个产生元素
1 [2 m0 \* O( d3 c# w  }8 I/ J4 |得到的值不一样:列表推导式得到的是一个列表;生成器表达式获取的是一个生成器
6 ]7 F1 {0 Z( F/ \6 c* l  f列表推导式一目了然,生成器表达式只是一个内存地址( \5 @" X0 R, q$ m
————————————————
% H) u' r" u. `6 E4 c版权声明:本文为CSDN博主「阿玮d博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
9 d& J* J# z" e  R  u9 `- a原文链接:https://blog.csdn.net/weixin_42511320/article/details/105676143, Z. B0 y, W* k  `/ _6 Y, }# r2 |4 L
$ w! D& j+ Z6 B5 N6 _) u: j

  Z5 k: W$ z: e9 O/ @7 a" T
作者: dwadasd    时间: 2020-6-24 20:40
多谢大神分享# o% Q& o' x7 T/ H

作者: dwadasd    时间: 2020-6-24 20:41
太好了太好了% a/ V5 r- K& N





欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5