数学建模社区-数学中国

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

作者: zhangtt123    时间: 2020-6-16 10:18
标题: Python 迭代器、生成器
一、可迭代对象
  s3 R3 T# j3 A字面意思:
; k8 b- N$ q$ ~, d; ?& t对象:Python 中一切皆为对象(巧了 Java 也是(手动滑稽))& D/ {+ }0 Z7 }2 D* ~; I* {
可迭代:可更新迭代,重复、循环的一个过程,每次更新迭代都会获得新的内容- h8 p6 @$ f2 {: Y" j8 E+ ?
专业角度:内部含有 '__iter__‘ 方法的对象
7 t( A- Q5 g2 ]  P目前学过的可迭代对象:str、list、tuple、dict、set、range、文件句柄等  |) {' D3 E3 M2 f
判断一个对象是否是可迭代对象:看是否有 '__iter__' 方法,dir() 可以获取一个对象的所有方法;或者使用 isinstance(object, collections.iterable) 来判断对象是否是可迭代对象的一个实例$ D/ H' r7 B+ e7 W) x/ m6 c* t3 o
' V& D5 L$ Z# T

( o' J* P- P1 X. d* c优点:% v+ u4 j7 `, }! g4 A0 o& k
存储的数据直接能显示,比较直观:比如直接 print 一个可迭代对象,就会调用 __str__ 方法(相当于 Java 中的 toString),把可迭代对象的值打印出来. C3 A3 K( q8 u- I
拥有较多的方法,操作方便:增删查改等
% H8 i1 ^) u+ n/ F5 x/ u5 g缺点:
1 v5 i0 w. a. r7 ]7 E+ c占用内存:一旦创建了一个可迭代对象,就会将该对象的内容全部加载到内存中3 E1 U4 `; r7 {: _! Q! e$ z
不能直接通过 for 循环,不能直接取值(通过索引、key等)。诸如通过 for i in iterable 这种形式获取元素实际上也是调用了 __iter__ 方法先将可迭代对象转换成迭代器再进行获取
4 G& j+ H1 E. g+ x二、迭代器
; C+ @# w/ M/ S字面意思:器,工具,迭代器也就是可以一直更新迭代取值的工具2 b6 m: X4 y" R0 K  A* e/ c
专业角度:内部含有 __iter__ 方法且含有 __next__ 方法的对象就是迭代器;或者使用 isinstance(object, collections.iterator) 来判断对象是否是可迭代对象的一个实例% f. w% C4 G3 b% b4 z4 L
把一个可迭代对象转换成迭代器:使用 iter() 方法或使用对象的 __iter__ 方法6 C4 ]. z) _0 |- U8 w9 }, k

" W$ O: @5 G, e0 l' a5 u迭代器取值:使用 next() 方法或对象的 __next__ 方法;当迭代器的值去玩了继续取,就会报StopIteration异常,所以一般使用迭代器需要做异常处理7 q3 N* h. i; x

1 D0 W2 U4 [& u4 c: [优点% z( c0 r8 F2 X- c/ ?: }
节省内存:迭代器并不会一次性将对象的值全部加载到内存中,而是需要时才加载(类似 sed)' x9 ^' S  w/ s, n
惰性机制:next 一次只取一个值,绝对不多取3 `5 B5 [) j0 Z
缺点:
6 y+ C# a# q, c速度慢:需要一直 next
; Z% j. X- R% P$ M4 ^. a& R不能回头:只能一直往下取值,取过的值没保存就没了`
- R# [6 g7 }, W不能直观的看到里面的数据
& {5 D/ Z: X. X( X0 C2 Z0 _9 h7 P- p( j三、可迭代对象与迭代器对比* h6 E) O0 V6 \$ O9 f: X2 q
可迭代对象:( s( z$ e3 [) J' U
私有方法多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等)
4 s: _/ T: q0 O8 @. ?7 `直观,可以直接看到里面的数据
* t8 h- q3 c3 i6 q# B3 \2 b占用内存! l0 H4 s; h/ `3 S+ u
不能直接通过循环迭代取值6 O8 [+ [5 r+ G$ f9 F! n* I; g
应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择
3 ?* s# r( U! C& h" c* y: c迭代器:2 _8 L& ]9 \* Z; {7 \: c* f4 g
节省内存,按需取值
  o* u6 ?/ Z8 U6 k# G5 }  @9 a可以直接通过循环迭代取值
4 i6 l+ r- C. e9 |0 V: [  V- g# J数据不直观,操作方法单一
! B+ d' u" t+ L$ h5 n应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择2 W2 w# T4 _0 @- e2 \
四、生成器( K7 P8 Y0 {! b
生成器的本质就是迭代器,唯一的区别是生成器是我们自己用代码构建的数据结构,迭代器是 Python 提供的,或者通过可迭代对象转化得来的  Y( q2 {( w3 {$ B
7 i( X# f% K* h# p# r
定义生成器的方式:( ]$ z2 Z5 a' C
通过生成器函数构建生成器
# b, w2 t: K& O- \( c8 S 111.png
: o/ p$ c; `& Q+ [: u: q8 F2 \
% ~/ _/ Y+ R# Q6 F# Y8 _# T0 [" X9 x% A1 N- m. h
这就是最简单的生成器函数。实际上这个 yield 就替代了 return,不仅将函数变成了生成器函数,还会将后面的值在调用 __next__ 的时候返回出来
1 A% B) H5 q6 ]6 e: G# F5 H3 p) C5 g$ |+ `: `
也可以在一个函数里定义多个 yield
7 j0 e* F7 I6 w4 z( r0 k7 C0 Z4 k. u4 H& a3 w
之前说过,生成器本质上还是迭代器,一个 yield 对应一个 next,当 next 的数量超过了 yield,就会报 StopIteration
. P3 |* B4 I9 b4 @/ v) f4 M( ]+ L. M4 z3 Y1 d4 g9 p5 l, E8 K
yield 与 return 的区别
& T4 D' q# r* }7 d3 K9 g. M- w
" c2 E# }0 E3 d& K" Rreturn一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值4 F) x+ m- o) U; c- s& e0 n- F% V
yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素5 Z+ E6 x7 U+ f6 [6 x
应用举例:( A7 I) L1 f. I+ T9 ~$ r5 B( J
+ h  x; w( l! L  h
买 5000 个包子,假设这个老板很厉害,一下子就把 5000 个包子做出来卖给我们,可是我们只有 5 个人,一下子吃不完,那包子就会冷掉、臭掉、被丢掉浪费了7 M4 l, I# Q1 q7 w1 X
222.png 5 L/ |( y8 y3 Z( R" C
( A4 ]* l3 c7 b0 X0 l* h
如果这个老板可以在我们需要多少个包子就做出来多少个包子的话,这样做出来的包子就不会被浪费了(比如我们每个人一口气能吃 40 个包子,那每次就做 200 个包子):- }; n/ y8 e7 r# R2 ?
3333.png
5 k( ~* V& S0 G8 V0 X/ m9 M/ H% T8 x: z6 z/ x, s& r* Q
除了 使用 next() 触发 yield 之外,生成器还有一种方法 send(),这个方法可以在调用 yield 的同时传值给生成器内部
+ U+ O' Y4 ?( O3 b
' M. T5 Z8 l! G# v0 a可以看到在使用 next() 的时候,只能获取到 yield 的值,但不能传递值, ?$ @$ j  p" d. K; c" ~+ X
1 S( J. _% p5 B' i9 _+ c; v2 K
在使用 send() 的时候,可以将参数传入生成器中使用
+ j% d* y* i0 @: k# M9 U  }( c  h6 E5 t* l  E; b, t
需要注意的是第一次不能直接调用 send() 传参,因为每次调用生成器的时候,实际上只会返回 yield 后面的内容,然后生成器就停止了(睡眠了?),而 send() 传入的参数要通过 yield 传入生成器中(每次调用生成器在 yield 停止,然后在 yield 恢复继续允许),第一次调用并没有 yield 给我们传入参数,可以使用 send(None),可以打断点自己分析一下
$ a7 D6 j$ C/ C, M' v$ q0 Q. d3 t2 b8 x7 b9 I0 k9 K4 N+ x7 F
yield 会将它后面跟着的对象直接返回,如果它后面跟着的是可迭代对象,也可以使用 yield from 将这个可迭代对象变成迭代器返回- G6 S/ l) n/ ]1 C. }* ]+ |
1 ^- i. U5 u! h0 ^% e

* }' w8 a1 o) c8 ]. O/ }- y, _yield from 是将列表中的每一个元素返回,所以写两个 yield from 并不会有交替执行的效果& o( I- y4 g$ m& W6 x' s% j9 i! v

! _6 r0 p' ^5 J+ a. f
/ J$ A$ s& z$ M( B9 J3 j
- B9 P% e1 |4 P2 t& O+ R4 Y" \通过推导式构建生成器' x0 B2 w5 G' u. ~- m: k
列表推导式:
( W6 Z' o/ x# z1 V4 u5 S9 M: K; s2 u! s5 v" }
生成器表达式:和列表推导式差不多,把 [] 改成 () 即可$ ]% I7 y% ?$ U4 v; q) S2 R6 ?
! A- o/ l; s4 u/ S( d& p- Q

0 m- t1 Y% k0 @8 d. Z  a( ?列表推导式和生成器推导式的区别:
- o% w) F9 T* F* [/ r$ ~: ?7 j7 E+ _3 P6 L' F7 g. t8 I- Y4 I
列表推导式比较耗内存,所有数据一次性加载到内存;而生成器表达式遵循迭代器协议,逐个产生元素  e) N2 ]- W  [
得到的值不一样:列表推导式得到的是一个列表;生成器表达式获取的是一个生成器
/ i+ ~7 B$ r" `9 X列表推导式一目了然,生成器表达式只是一个内存地址5 m' W4 K( e+ h9 O
————————————————
6 Q* L' ?: a2 }: e" L) q版权声明:本文为CSDN博主「阿玮d博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
8 k* b1 b2 d  R; D" j4 V原文链接:https://blog.csdn.net/weixin_42511320/article/details/105676143
  Y. g$ w: ^3 l1 ]9 {2 y* L- A- W' }$ t/ h2 L
& w- h6 o6 f% O5 [( U

作者: dwadasd    时间: 2020-6-24 20:40
多谢大神分享
) H9 c' U2 E/ V! n* e$ [- b
作者: dwadasd    时间: 2020-6-24 20:41
太好了太好了6 _4 y' }+ P& R5 f0 a8 z9 k: [





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