数学建模社区-数学中国

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

作者: zhangtt123    时间: 2020-6-16 10:18
标题: Python 迭代器、生成器
一、可迭代对象
; Q9 A  L% b6 G2 E字面意思:
- n7 Q  Y; h, n. b对象:Python 中一切皆为对象(巧了 Java 也是(手动滑稽))
0 t/ r1 v3 c% r2 i" B可迭代:可更新迭代,重复、循环的一个过程,每次更新迭代都会获得新的内容
5 _7 f/ F& X( A4 n. D, B专业角度:内部含有 '__iter__‘ 方法的对象, {  T, d3 I0 S
目前学过的可迭代对象:str、list、tuple、dict、set、range、文件句柄等8 J  |: }" n& r  Y! ?" e5 i# L
判断一个对象是否是可迭代对象:看是否有 '__iter__' 方法,dir() 可以获取一个对象的所有方法;或者使用 isinstance(object, collections.iterable) 来判断对象是否是可迭代对象的一个实例- ^8 F5 E6 }$ Y
' v2 _2 _  O) |5 y5 K: a2 I

( i! G, W% O+ E% Q' G# Y5 L优点:
: G$ N. G  E1 d0 Y存储的数据直接能显示,比较直观:比如直接 print 一个可迭代对象,就会调用 __str__ 方法(相当于 Java 中的 toString),把可迭代对象的值打印出来
7 V! M% W  w) F$ e拥有较多的方法,操作方便:增删查改等8 X# v" F7 @7 E& }5 H
缺点:9 z. ^& K2 ]4 C1 p( y
占用内存:一旦创建了一个可迭代对象,就会将该对象的内容全部加载到内存中1 T6 x# u( j: e+ Q! N& A4 i
不能直接通过 for 循环,不能直接取值(通过索引、key等)。诸如通过 for i in iterable 这种形式获取元素实际上也是调用了 __iter__ 方法先将可迭代对象转换成迭代器再进行获取* v* A* W( |. k  i. g: p3 b
二、迭代器
% I& [5 |7 W8 K0 K8 ^字面意思:器,工具,迭代器也就是可以一直更新迭代取值的工具- n- ?" P$ c& j! ]: [9 l% U( i
专业角度:内部含有 __iter__ 方法且含有 __next__ 方法的对象就是迭代器;或者使用 isinstance(object, collections.iterator) 来判断对象是否是可迭代对象的一个实例+ m0 M1 U5 S# ]8 p6 I7 I: e4 X6 n
把一个可迭代对象转换成迭代器:使用 iter() 方法或使用对象的 __iter__ 方法
9 y5 M( @1 q+ v3 Y0 o! t8 R6 \: ?: j4 k4 A, b: D
迭代器取值:使用 next() 方法或对象的 __next__ 方法;当迭代器的值去玩了继续取,就会报StopIteration异常,所以一般使用迭代器需要做异常处理1 h5 W+ h2 m, W: Y5 S

! [  H$ Z' ^2 Y+ b* z9 Q6 G6 n优点- I( n9 A8 @$ m2 `7 A  ?
节省内存:迭代器并不会一次性将对象的值全部加载到内存中,而是需要时才加载(类似 sed)
  e$ G. O/ P- z& P  q惰性机制:next 一次只取一个值,绝对不多取
% l, E+ G2 x8 I& \  X* o& W缺点:8 m* [8 _0 q+ ]2 x
速度慢:需要一直 next- X; f  w% W, d$ A9 j/ W. n0 s0 A
不能回头:只能一直往下取值,取过的值没保存就没了`, P. j/ h9 n4 \0 t* u( |
不能直观的看到里面的数据
% W; _4 _/ a5 Y- J  d三、可迭代对象与迭代器对比
% @  X; S! P7 N8 ?0 z9 a1 r, Y可迭代对象:
$ m- R9 T! ?+ {- @$ z私有方法多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等)) d0 ?+ j5 D. w5 {3 M
直观,可以直接看到里面的数据
0 M) r9 e* C% m& ?: }9 d9 N% n占用内存, N% I* y7 C2 x- R) Q0 m
不能直接通过循环迭代取值
% t! w5 }8 f) E% B! Y应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择7 l/ V0 q9 }- {% o, R; Z
迭代器:
! n; w3 h5 c" e. \7 m2 E' ~节省内存,按需取值
6 F: _& c( q/ M9 @2 L可以直接通过循环迭代取值
% t" L3 G4 ^% B数据不直观,操作方法单一
$ S# G7 K7 M" n4 O% V应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择- ~- l5 t  F3 S, f7 w: f+ w) w
四、生成器
# V* `6 i, a7 H+ v生成器的本质就是迭代器,唯一的区别是生成器是我们自己用代码构建的数据结构,迭代器是 Python 提供的,或者通过可迭代对象转化得来的( c. ~% ^1 T+ N- w& @
% ^1 q$ g2 @. `& h& n
定义生成器的方式:
( y" S  W1 M: B' N' `8 ?通过生成器函数构建生成器  I0 z, t. N# E  m) W
111.png
/ Y6 _# }' }3 `: Q+ Q. ~( s  d, h' |" y- n% U

1 G# I( t5 G6 S  k这就是最简单的生成器函数。实际上这个 yield 就替代了 return,不仅将函数变成了生成器函数,还会将后面的值在调用 __next__ 的时候返回出来
. ^  `! G! [; `
) j1 f. J" @( W& @也可以在一个函数里定义多个 yield
+ V. ]; U4 M. ?2 b0 C" ?% {! u+ O* D7 x
之前说过,生成器本质上还是迭代器,一个 yield 对应一个 next,当 next 的数量超过了 yield,就会报 StopIteration, p7 [% M4 Q( m  r9 x7 ?

. y) I* P' q) T4 H, d5 j, j. G6 g) yyield 与 return 的区别  z1 `# ^& n& m% Q+ @
& ]9 w  I4 s2 R$ |
return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值* E9 i( S! g9 R; G7 W; }9 Y2 q3 \6 X' [# z
yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素1 y! F* P/ n' c2 B
应用举例:
$ K; w; f. a+ D% J; h2 k
$ @& g( o4 e5 @买 5000 个包子,假设这个老板很厉害,一下子就把 5000 个包子做出来卖给我们,可是我们只有 5 个人,一下子吃不完,那包子就会冷掉、臭掉、被丢掉浪费了. w7 J/ u/ ~: |5 ]4 m
222.png
2 }( _2 ?! ]4 A; {4 S# w# N
& N/ O7 m( o% c; J# \; M- o如果这个老板可以在我们需要多少个包子就做出来多少个包子的话,这样做出来的包子就不会被浪费了(比如我们每个人一口气能吃 40 个包子,那每次就做 200 个包子):; G7 U: F  n9 Q- q; E3 i7 M+ w2 Y
3333.png
# T  G, u# r1 \7 \( T5 N5 w% c
+ s# A$ [6 a- t' t8 X除了 使用 next() 触发 yield 之外,生成器还有一种方法 send(),这个方法可以在调用 yield 的同时传值给生成器内部
6 I2 I3 J- \6 {$ L! P& h4 I/ ]
9 l! S$ j- u0 @# O; j% w可以看到在使用 next() 的时候,只能获取到 yield 的值,但不能传递值
- K0 C- m) O8 p* `8 r( o: r& N+ G1 Y# V3 l/ y; j1 R* `/ u$ w
在使用 send() 的时候,可以将参数传入生成器中使用
8 v- P) k3 R& j  U
/ H) J7 V$ f/ a) ~需要注意的是第一次不能直接调用 send() 传参,因为每次调用生成器的时候,实际上只会返回 yield 后面的内容,然后生成器就停止了(睡眠了?),而 send() 传入的参数要通过 yield 传入生成器中(每次调用生成器在 yield 停止,然后在 yield 恢复继续允许),第一次调用并没有 yield 给我们传入参数,可以使用 send(None),可以打断点自己分析一下) ?  u4 G3 i7 k! W$ V8 Y3 K) C
: l" X9 J+ o+ O( {: |
yield 会将它后面跟着的对象直接返回,如果它后面跟着的是可迭代对象,也可以使用 yield from 将这个可迭代对象变成迭代器返回" I, ~" {! j8 ?) Z5 r* U& t; `1 X! e. m
3 m, ?6 S, L$ p3 x

3 A: m  v, c$ `yield from 是将列表中的每一个元素返回,所以写两个 yield from 并不会有交替执行的效果. H" T" T& _: Q% R9 M

4 g/ p/ C2 ?  I2 i- I
: L; s/ f0 B- D$ G  @, c5 Q4 x8 ]
" I' U, B8 x5 u: p! J8 d通过推导式构建生成器; i, e' E! P% h2 N0 k7 P$ N$ D
列表推导式:
& W* K# V! M6 i( w$ {) e) z
( ~/ ~* G. a# M$ i生成器表达式:和列表推导式差不多,把 [] 改成 () 即可
0 c3 F* Q% p) U$ y0 n* T9 L5 u8 D, c  G+ S: g* T

( b% \( V0 S6 }9 ~, r" B  R/ Q列表推导式和生成器推导式的区别:
. w  Q; @4 M7 W! A, b$ Q# A! ]% D+ X
列表推导式比较耗内存,所有数据一次性加载到内存;而生成器表达式遵循迭代器协议,逐个产生元素
( D: R( @6 b. n6 \6 I9 j7 H得到的值不一样:列表推导式得到的是一个列表;生成器表达式获取的是一个生成器9 [* A, E# v" X  ?+ p# [3 u2 \! ~
列表推导式一目了然,生成器表达式只是一个内存地址7 Q1 D! I7 h( n6 ~9 z$ J
————————————————9 m3 T& P" }  @2 K" H
版权声明:本文为CSDN博主「阿玮d博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。  x' u# K% B' H& }9 K3 `/ w9 {8 F
原文链接:https://blog.csdn.net/weixin_42511320/article/details/105676143
- n' ^, A, j" h  o4 I% v7 X! g7 v7 Z& b6 s" _1 L

. N1 g7 G5 q# v, n" _8 g! {: E: D$ }5 }
作者: dwadasd    时间: 2020-6-24 20:40
多谢大神分享
1 g8 K5 m$ b% w% q# W9 G$ q
作者: dwadasd    时间: 2020-6-24 20:41
太好了太好了
& C. h7 p" A* d+ v. n0 Y& Z0 e




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